Local branch had diverged from origin/master (sibling commits on the same base). Rewrote local history linearly on top of origin/master, folding in all local content: exa/hot-platform discovery hunters, leaks ledger, nightly sweep orchestrator, updated .gitignore and skill docs, plus the latest hunt output and vault state. Remote-only files (vault keys, channel scripts) were restored rather than dropped, so the resulting tree is a full union of both sides.
1 line
284 KiB
JSON
1 line
284 KiB
JSON
{"repo": "befamousvideo/betty", "files": {"LEAN/Betty/betty-n8n/docker-compose.yml": {"c752ce706cf1702f80774980639568e0ce607a36": {"t": 1785746993.208478, "n": 21905, "h": "d5e3bb63aeb98c66", "c": "name: betty-stack\n\nservices:\n postgres:\n image: postgres:16-alpine\n container_name: betty-postgres\n environment:\n POSTGRES_USER: ${POSTGRES_USER:-n8n}\n POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n POSTGRES_DB: ${POSTGRES_DB:-n8n}\n volumes:\n - postgres_data:/var/lib/postgresql/data\n healthcheck:\n test: [\"CMD-SHELL\", \"pg_isready -U ${POSTGRES_USER:-n8n} -d ${POSTGRES_DB:-n8n}\"]\n interval: 10s\n timeout: 5s\n retries: 10\n start_period: 15s\n restart: unless-stopped\n security_opt:\n - no-new-privileges:true\n networks:\n - betty-net\n\n chroma:\n image: chromadb/chroma:0.5.23\n container_name: betty-chroma\n volumes:\n - chroma_data:/chroma/chroma\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/api/v1/heartbeat', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 15s\n restart: unless-stopped\n security_opt:\n - no-new-privileges:true\n networks:\n - betty-net\n\n ollama:\n image: ollama/ollama:latest\n container_name: betty-ollama\n ports:\n - \"127.0.0.1:${OLLAMA_HOST_PORT:-11435}:11434\"\n volumes:\n - ollama_data:/root/.ollama\n healthcheck:\n test: [\"CMD-SHELL\", \"ollama list >/dev/null 2>&1 || exit 1\"]\n interval: 20s\n timeout: 10s\n retries: 6\n start_period: 30s\n restart: unless-stopped\n security_opt:\n - no-new-privileges:true\n networks:\n - betty-net\n\n ollama-wrapper:\n build:\n context: ./ollama-wrapper\n image: betty/ollama-wrapper:local\n container_name: betty-ollama-wrapper\n depends_on:\n ollama:\n condition: service_healthy\n environment:\n OLLAMA_URL: http://ollama:11434\n API_USERNAME: ${WRAPPER_API_USERNAME:?set WRAPPER_API_USERNAME in .env}\n API_PASSWORD: ${WRAPPER_API_PASSWORD:?set WRAPPER_API_PASSWORD in .env}\n API_BEARER_TOKEN: ${WRAPPER_API_BEARER_TOKEN:-}\n WRAPPER_REQUEST_TIMEOUT_SECONDS: ${WRAPPER_REQUEST_TIMEOUT_SECONDS:-300}\n LOG_LEVEL: ${WRAPPER_LOG_LEVEL:-info}\n ports:\n - \"127.0.0.1:${WRAPPER_HOST_PORT:-18000}:8000\"\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 10s\n read_only: true\n tmpfs:\n - /tmp\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n betty-memory:\n build:\n context: ./services/memory\n image: betty/memory-service:local\n container_name: betty-memory\n depends_on:\n postgres:\n condition: service_healthy\n chroma:\n condition: service_healthy\n ollama:\n condition: service_healthy\n environment:\n BETTY_MEMORY_POSTGRES_HOST: postgres\n BETTY_MEMORY_POSTGRES_PORT: 5432\n BETTY_MEMORY_POSTGRES_DB: ${POSTGRES_DB:-n8n}\n BETTY_MEMORY_POSTGRES_USER: ${POSTGRES_USER:-n8n}\n BETTY_MEMORY_POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n BETTY_MEMORY_CHROMA_HOST: chroma\n BETTY_MEMORY_CHROMA_PORT: 8000\n BETTY_MEMORY_COLLECTION: ${BETTY_MEMORY_COLLECTION:-betty-v1-facts}\n BETTY_MEMORY_OLLAMA_URL: http://ollama:11434\n BETTY_MEMORY_EMBED_MODEL: ${BETTY_MEMORY_EMBED_MODEL:-nomic-embed-text}\n BETTY_MEMORY_SEMANTIC_DISTANCE_THRESHOLD: ${BETTY_MEMORY_SEMANTIC_DISTANCE_THRESHOLD:-0.45}\n BETTY_MEMORY_LOG_LEVEL: ${BETTY_MEMORY_LOG_LEVEL:-info}\n BETTY_MEMORY_DEFAULT_TIMEZONE: ${GENERIC_TIMEZONE:-America/Los_Angeles}\n BETTY_OWNER_NAME: ${BETTY_OWNER_NAME:-Vincent}\n BETTY_HOME_CITY: ${BETTY_HOME_CITY:-Corona Del Mar, CA}\n ports:\n - \"127.0.0.1:${BETTY_MEMORY_HOST_PORT:-18100}:8000\"\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 15s\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n betty-actions:\n build:\n context: ./services/actions\n image: betty/actions-service:local\n container_name: betty-actions\n depends_on:\n postgres:\n condition: service_healthy\n environment:\n BETTY_ACTIONS_POSTGRES_HOST: postgres\n BETTY_ACTIONS_POSTGRES_PORT: 5432\n BETTY_ACTIONS_POSTGRES_DB: ${POSTGRES_DB:-n8n}\n BETTY_ACTIONS_POSTGRES_USER: ${POSTGRES_USER:-n8n}\n BETTY_ACTIONS_POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n BETTY_ACTIONS_DEFAULT_TIMEZONE: ${GENERIC_TIMEZONE:-America/Los_Angeles}\n GOOGLE_CALENDAR_CLIENT_ID: ${GOOGLE_CALENDAR_CLIENT_ID:-}\n GOOGLE_CALENDAR_CLIENT_SECRET: ${GOOGLE_CALENDAR_CLIENT_SECRET:-}\n ports:\n - \"127.0.0.1:${BETTY_ACTIONS_HOST_PORT:-18110}:8000\"\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 15s\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n betty-lookup:\n build:\n context: ./services/lookup\n image: betty/lookup-service:local\n container_name: betty-lookup\n depends_on:\n postgres:\n condition: service_healthy\n ollama-wrapper:\n condition: service_healthy\n environment:\n BETTY_LOOKUP_POSTGRES_HOST: postgres\n BETTY_LOOKUP_POSTGRES_PORT: 5432\n BETTY_LOOKUP_POSTGRES_DB: ${POSTGRES_DB:-n8n}\n BETTY_LOOKUP_POSTGRES_USER: ${POSTGRES_USER:-n8n}\n BETTY_LOOKUP_POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n BETTY_LOOKUP_DEFAULT_TIMEZONE: ${GENERIC_TIMEZONE:-America/Los_Angeles}\n BETTY_OWNER_NAME: ${BETTY_OWNER_NAME:-Vincent}\n BETTY_HOME_CITY: ${BETTY_HOME_CITY:-Corona Del Mar, CA}\n BRAVE_ENABLED: ${BRAVE_ENABLED:-false}\n SEARXNG_ENABLED: ${SEARXNG_ENABLED:-true}\n SEARXNG_BASE_URL: ${SEARXNG_BASE_URL:-http://host.docker.internal:8080}\n BETTY_COST_DAILY_CAP_USD: ${BETTY_COST_DAILY_CAP_USD:-5}\n BETTY_COST_MONTHLY_CAP_USD: ${BETTY_COST_MONTHLY_CAP_USD:-50}\n BETTY_COST_WARNING_RATIO: ${BETTY_COST_WARNING_RATIO:-0.8}\n BRAVE_SEARCH_API_KEY: ${BRAVE_SEARCH_API_KEY:-}\n BETTY_LOOKUP_WRAPPER_URL: http://ollama-wrapper:8000/v1/chat/completions\n BETTY_LOOKUP_WRAPPER_USERNAME: ${WRAPPER_API_USERNAME:?set WRAPPER_API_USERNAME in .env}\n BETTY_LOOKUP_WRAPPER_PASSWORD: ${WRAPPER_API_PASSWORD:?set WRAPPER_API_PASSWORD in .env}\n BETTY_LOOKUP_WRAPPER_BEARER_TOKEN: ${WRAPPER_API_BEARER_TOKEN:-}\n BETTY_LOOKUP_MODEL: ${BETTY_LOOKUP_MODEL:-}\n BETTY_LOOKUP_WRAPPER_TIMEOUT_SECONDS: ${WRAPPER_REQUEST_TIMEOUT_SECONDS:-120}\n ports:\n - \"127.0.0.1:${BETTY_LOOKUP_HOST_PORT:-18130}:8000\"\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 15s\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n betty-brain:\n build:\n context: ./services/brain\n image: betty/brain-service:local\n container_name: betty-brain\n depends_on:\n postgres:\n condition: service_healthy\n betty-memory:\n condition: service_healthy\n betty-lookup:\n condition: service_healthy\n environment:\n BETTY_BRAIN_POSTGRES_HOST: postgres\n BETTY_BRAIN_POSTGRES_PORT: 5432\n BETTY_BRAIN_POSTGRES_DB: ${POSTGRES_DB:-n8n}\n BETTY_BRAIN_POSTGRES_USER: ${POSTGRES_USER:-n8n}\n BETTY_BRAIN_POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n BETTY_BRAIN_MODEL: ${BETTY_BRAIN_MODEL:-claude-haiku-4-5}\n BETTY_BRAIN_MAX_TOKENS: ${BETTY_BRAIN_MAX_TOKENS:-2000}\n BETTY_BRAIN_MAX_AGENT_ITERATIONS: ${BETTY_BRAIN_MAX_AGENT_ITERATIONS:-8}\n BETTY_BRAIN_MEMORY_URL: http://betty-memory:8000\n BETTY_BRAIN_LOOKUP_URL: http://betty-lookup:8000\n BETTY_BRAIN_OPERATOR_URL: http://betty-operator:8000\n BETTY_BRAIN_ACTIONS_URL: http://betty-actions:8000\n BETTY_BRAIN_TOOL_HTTP_TIMEOUT: ${BETTY_BRAIN_TOOL_HTTP_TIMEOUT:-30}\n BETTY_BRAIN_LOOKUP_HTTP_TIMEOUT: ${BETTY_BRAIN_LOOKUP_HTTP_TIMEOUT:-60}\n BETTY_OWNER_EMAIL: ${BETTY_OWNER_EMAIL:-vincentthinks@gmail.com}\n BETTY_SLACK_BOT_TOKEN: ${BETTY_SLACK_BOT_TOKEN:-}\n BETTY_SLACK_DEFAULT_DELEGATION_CHANNEL: ${BETTY_SLACK_DEFAULT_DELEGATION_CHANNEL:-#betty-station}\n ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:?set ANTHROPIC_API_KEY in .env}\n BETTY_COST_DAILY_CAP_USD: ${BETTY_COST_DAILY_CAP_USD:-1}\n BETTY_COST_MONTHLY_CAP_USD: ${BETTY_COST_MONTHLY_CAP_USD:-30}\n BETTY_COST_WARNING_RATIO: ${BETTY_COST_WARNING_RATIO:-0.8}\n BETTY_OWNER_NAME: ${BETTY_OWNER_NAME:-Vincent}\n BETTY_HOME_CITY: ${BETTY_HOME_CITY:-Rancho Santa Margarita, CA}\n ports:\n - \"127.0.0.1:${BETTY_BRAIN_HOST_PORT:-18140}:8000\"\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 15s\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n betty-daemon:\n build:\n context: ./services/betty-slack-daemon\n image: betty/betty-slack-daemon:local\n container_name: betty-daemon\n depends_on:\n betty-brain:\n condition: service_healthy\n betty-memory:\n condition: service_healthy\n environment:\n BETTY_SLACK_APP_TOKEN: ${BETTY_SLACK_APP_TOKEN:?set BETTY_SLACK_APP_TOKEN in .env after creating Betty's Slack app}\n BETTY_SLACK_BOT_TOKEN: ${BETTY_SLACK_BOT_TOKEN:?set BETTY_SLACK_BOT_TOKEN in .env after creating Betty's Slack app}\n BETTY_BRAIN_URL: http://betty-brain:8000\n BETTY_BRAIN_TIMEOUT_SECONDS: ${BETTY_BRAIN_TIMEOUT_SECONDS:-60}\n BETTY_DAEMON_MEMORY_URL: http://betty-memory:8000\n BETTY_DAEMON_MEMORY_TIMEOUT_SECONDS: ${BETTY_DAEMON_MEMORY_TIMEOUT_SECONDS:-15}\n BETTY_OWNER_SLACK_USER_ID: ${BETTY_OWNER_SLACK_USER_ID:-}\n BETTY_DAEMON_REMINDER_POLL_INTERVAL_SECONDS: ${BETTY_DAEMON_REMINDER_POLL_INTERVAL_SECONDS:-60}\n BETTY_DAEMON_REMINDER_LOOKAHEAD_SECONDS: ${BETTY_DAEMON_REMINDER_LOOKAHEAD_SECONDS:-60}\n BETTY_DAEMON_DATA_DIR: /data\n BETTY_DAEMON_THREAD_KEEP_MSGS: ${BETTY_DAEMON_THREAD_KEEP_MSGS:-20}\n volumes:\n - ./data/betty-daemon:/data\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n betty-whisper:\n build:\n context: ./services/whisper\n image: betty/whisper-service:local\n container_name: betty-whisper\n environment:\n WHISPER_MODEL: ${BETTY_WHISPER_MODEL:-Systran/faster-distil-whisper-large-v3}\n WHISPER_DEVICE: ${BETTY_WHISPER_DEVICE:-cuda}\n WHISPER_COMPUTE_TYPE: ${BETTY_WHISPER_COMPUTE_TYPE:-float16}\n WHISPER_BATCH_SIZE: ${BETTY_WHISPER_BATCH_SIZE:-16}\n WHISPER_BEAM_SIZE: ${BETTY_WHISPER_BEAM_SIZE:-1}\n WHISPER_VAD_FILTER: ${BETTY_WHISPER_VAD_FILTER:-true}\n WHISPER_DEFAULT_LANGUAGE: ${BETTY_WHISPER_DEFAULT_LANGUAGE:-en}\n WHISPER_DOWNLOAD_ROOT: /data\n volumes:\n - betty_whisper_data:/data\n deploy:\n resources:\n reservations:\n devices:\n - driver: nvidia\n count: 1\n capabilities: [gpu]\n healthcheck:\n test: [\"CMD\", \"python3\", \"-c\", \"import urllib.request,sys,json;d=json.load(urllib.request.urlopen('http://localhost:8000/health',timeout=5));sys.exit(0 if d.get('ready') else 1)\"]\n interval: 30s\n timeout: 10s\n retries: 5\n start_period: 180s\n restart: unless-stopped\n networks:\n - betty-net\n\n betty-operator:\n build:\n context: ./services/operator\n image: betty/operator-service:local\n container_name: betty-operator\n depends_on:\n postgres:\n condition: service_healthy\n betty-memory:\n condition: service_healthy\n betty-actions:\n condition: service_healthy\n betty-lookup:\n condition: service_healthy\n environment:\n BETTY_OPERATOR_POSTGRES_HOST: postgres\n BETTY_OPERATOR_POSTGRES_PORT: 5432\n BETTY_OPERATOR_POSTGRES_DB: ${POSTGRES_DB:-n8n}\n BETTY_OPERATOR_POSTGRES_USER: ${POSTGRES_USER:-n8n}\n BETTY_OPERATOR_POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n BETTY_OPERATOR_MEMORY_URL: http://betty-memory:8000\n BETTY_OPERATOR_ACTIONS_URL: http://betty-actions:8000\n BETTY_OPERATOR_LOOKUP_URL: http://betty-lookup:8000\n BETTY_OPERATOR_BRAIN_URL: http://betty-brain:8000\n BETTY_OPERATOR_BRAIN_TIMEOUT_SECONDS: ${BETTY_OPERATOR_BRAIN_TIMEOUT_SECONDS:-60}\n BETTY_OPERATOR_BRAIN_DEFAULT_ENABLED: ${BETTY_OPERATOR_BRAIN_DEFAULT_ENABLED:-true}\n BETTY_OPERATOR_WRAPPER_URL: http://ollama-wrapper:8000/v1/chat/completions\n BETTY_OPERATOR_WRAPPER_USERNAME: ${WRAPPER_API_USERNAME:?set WRAPPER_API_USERNAME in .env}\n BETTY_OPERATOR_WRAPPER_PASSWORD: ${WRAPPER_API_PASSWORD:?set WRAPPER_API_PASSWORD in .env}\n BETTY_OPERATOR_WRAPPER_BEARER_TOKEN: ${WRAPPER_API_BEARER_TOKEN:-}\n BETTY_OPERATOR_WRAPPER_TIMEOUT_SECONDS: ${WRAPPER_REQUEST_TIMEOUT_SECONDS:-120}\n BETTY_OPERATOR_DEFAULT_TIMEZONE: ${GENERIC_TIMEZONE:-America/Los_Angeles}\n BETTY_PUBLIC_BASE_URL: ${BETTY_PUBLIC_BASE_URL:-https://YOUR_PUBLIC_URL}\n BETTY_OWNER_PHONE: ${BETTY_OWNER_PHONE:-}\n BETTY_OWNER_NAME: ${BETTY_OWNER_NAME:-Vincent}\n BETTY_HOME_CITY: ${BETTY_HOME_CITY:-Corona Del Mar, CA}\n BETTY_OPERATOR_STT_PROVIDER: ${BETTY_OPERATOR_STT_PROVIDER:-whisper_local}\n BETTY_OPERATOR_WHISPER_MODEL: ${BETTY_OPERATOR_WHISPER_MODEL:-base.en}\n BETTY_OPERATOR_WHISPER_DEVICE: ${BETTY_OPERATOR_WHISPER_DEVICE:-cpu}\n BETTY_OPERATOR_WHISPER_COMPUTE_TYPE: ${BETTY_OPERATOR_WHISPER_COMPUTE_TYPE:-int8}\n BETTY_OPERATOR_WHISPER_DATA_DIR: ${BETTY_OPERATOR_WHISPER_DATA_DIR:-/app/data/whisper}\n BETTY_OPERATOR_WHISPER_REMOTE_URL: ${BETTY_OPERATOR_WHISPER_REMOTE_URL:-http://betty-whisper:8000}\n BETTY_OPERATOR_WHISPER_REMOTE_TIMEOUT_SECONDS: ${BETTY_OPERATOR_WHISPER_REMOTE_TIMEOUT_SECONDS:-90}\n BETTY_MODEL_MODE: ${BETTY_MODEL_MODE:-default}\n BETTY_DEFAULT_CHAT_PROVIDER: ${BETTY_DEFAULT_CHAT_PROVIDER:-auto}\n BETTY_DEFAULT_CHAT_MODEL: ${BETTY_DEFAULT_CHAT_MODEL:-deepseek-chat}\n BETTY_DEFAULT_LOCAL_CHAT_MODEL: ${BETTY_DEFAULT_LOCAL_CHAT_MODEL:-llama3.1:8b}\n BETTY_MODEL_DOLPHIN: ${BETTY_MODEL_DOLPHIN:-dolphin-llama3:70b}\n BETTY_MODEL_MEDICAL: ${BETTY_MODEL_MEDICAL:-medllama2}\n BETTY_MODEL_MUSIC: ${BETTY_MODEL_MUSIC:-llamusic/llamusic}\n BETTY_MODEL_IMAGE: ${BETTY_MODEL_IMAGE:-llava:13b}\n # Slice 8: photo/scan vision. DGX direct (bypasses ollama-wrapper since wrapper\n # talks to local betty-ollama which has no LLaVA). Set to ollama-wrapper URL\n # if DGX is unreachable and you've pulled llava:13b locally.\n BETTY_VISION_OLLAMA_URL: ${BETTY_VISION_OLLAMA_URL:-http://192.168.68.79:11434}\n BETTY_VISION_TIMEOUT_SECONDS: ${BETTY_VISION_TIMEOUT_SECONDS:-90}\n # Cloud vision tier (Photo+ button → Haiku 4.5 vision via Anthropic API).\n ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-}\n BETTY_VISION_CLOUD_MODEL: ${BETTY_VISION_CLOUD_MODEL:-claude-haiku-4-5}\n BETTY_VISION_CLOUD_INPUT_COST_PER_1M_USD: ${BETTY_VISION_CLOUD_INPUT_COST_PER_1M_USD:-1.0}\n BETTY_VISION_CLOUD_OUTPUT_COST_PER_1M_USD: ${BETTY_VISION_CLOUD_OUTPUT_COST_PER_1M_USD:-5.0}\n BETTY_MODEL_WRITE: ${BETTY_MODEL_WRITE:-HammerAI/mythomax-l2}\n BETTY_MODEL_CODE: ${BETTY_MODEL_CODE:-qwen2.5-coder:32b}\n BETTY_MODEL_RESEARCH: ${BETTY_MODEL_RESEARCH:-nemotron}\n BETTY_MODEL_FAST: ${BETTY_MODEL_FAST:-gemma2:27b}\n DEEPSEEK_API_KEY: ${DEEPSEEK_API_KEY:-}\n DEEPSEEK_BASE_URL: ${DEEPSEEK_BASE_URL:-https://api.deepseek.com}\n BETTY_DEEPSEEK_MODEL: ${BETTY_DEEPSEEK_MODEL:-deepseek-chat}\n BETTY_DEEPSEEK_REASONER_MODEL: ${BETTY_DEEPSEEK_REASONER_MODEL:-deepseek-reasoner}\n BETTY_DEEPSEEK_TIMEOUT_SECONDS: ${BETTY_DEEPSEEK_TIMEOUT_SECONDS:-120}\n BETTY_DEEPSEEK_CHAT_INPUT_COST_PER_1M_USD: ${BETTY_DEEPSEEK_CHAT_INPUT_COST_PER_1M_USD:-0.27}\n BETTY_DEEPSEEK_CHAT_OUTPUT_COST_PER_1M_USD: ${BETTY_DEEPSEEK_CHAT_OUTPUT_COST_PER_1M_USD:-1.10}\n BETTY_DEEPSEEK_REASONER_INPUT_COST_PER_1M_USD: ${BETTY_DEEPSEEK_REASONER_INPUT_COST_PER_1M_USD:-0.55}\n BETTY_DEEPSEEK_REASONER_OUTPUT_COST_PER_1M_USD: ${BETTY_DEEPSEEK_REASONER_OUTPUT_COST_PER_1M_USD:-2.19}\n KOKORO_ENABLED: ${KOKORO_ENABLED:-true}\n KOKORO_DEFAULT_VOICE: ${KOKORO_DEFAULT_VOICE:-af_bella}\n BETTY_OPERATOR_KOKORO_SPEED: ${BETTY_OPERATOR_KOKORO_SPEED:-1.0}\n BETTY_OPERATOR_KOKORO_DATA_DIR: ${BETTY_OPERATOR_KOKORO_DATA_DIR:-/app/data/kokoro}\n ELEVENLABS_ENABLED: ${ELEVENLABS_ENABLED:-true}\n TWILIO_ENABLED: ${TWILIO_ENABLED:-true}\n VAPI_ENABLED: ${VAPI_ENABLED:-true}\n BETTY_COST_DAILY_CAP_USD: ${BETTY_COST_DAILY_CAP_USD:-5}\n BETTY_COST_MONTHLY_CAP_USD: ${BETTY_COST_MONTHLY_CAP_USD:-50}\n BETTY_COST_WARNING_RATIO: ${BETTY_COST_WARNING_RATIO:-0.8}\n BETTY_ELEVENLABS_COST_PER_1K_CHARS_USD: ${BETTY_ELEVENLABS_COST_PER_1K_CHARS_USD:-0.30}\n BETTY_TWILIO_SMS_COST_USD: ${BETTY_TWILIO_SMS_COST_USD:-0.008}\n BETTY_VAPI_COST_PER_MINUTE_USD: ${BETTY_VAPI_COST_PER_MINUTE_USD:-0.075}\n ELEVENLABS_API_KEY: ${ELEVENLABS_API_KEY:-}\n ELEVENLABS_VOICE_ID: ${ELEVENLABS_VOICE_ID:-4tRn1lSkEn13EVTuqb0g}\n ELEVENLABS_MODEL_ID: ${ELEVENLABS_MODEL_ID:-eleven_turbo_v2_5}\n PIPER_VOICE_MODEL: ${PIPER_VOICE_MODEL:-en_US-lessac-medium}\n BETTY_OPERATOR_PIPER_DATA_DIR: ${BETTY_OPERATOR_PIPER_DATA_DIR:-/app/data/piper}\n GOOGLE_CALENDAR_CLIENT_ID: ${GOOGLE_CALENDAR_CLIENT_ID:-}\n GOOGLE_CALENDAR_CLIENT_SECRET: ${GOOGLE_CALENDAR_CLIENT_SECRET:-}\n GOOGLE_CALENDAR_OAUTH_REDIRECT_URI: ${GOOGLE_CALENDAR_OAUTH_REDIRECT_URI:-}\n BETTY_OPERATOR_RATE_LIMIT_WINDOW_SECONDS: ${BETTY_OPERATOR_RATE_LIMIT_WINDOW_SECONDS:-60}\n BETTY_OPERATOR_ELEVENLABS_RATE_LIMIT: ${BETTY_OPERATOR_ELEVENLABS_RATE_LIMIT:-20}\n BETTY_OPERATOR_TWILIO_RATE_LIMIT: ${BETTY_OPERATOR_TWILIO_RATE_LIMIT:-20}\n VAPI_API_KEY: ${VAPI_API_KEY:-}\n VAPI_ASSISTANT_ID: ${VAPI_ASSISTANT_ID:-}\n VAPI_WEBHOOK_SECRET: ${VAPI_WEBHOOK_SECRET:-}\n TWILIO_ACCOUNT_SID: ${TWILIO_ACCOUNT_SID:-}\n TWILIO_AUTH_TOKEN: ${TWILIO_AUTH_TOKEN:-}\n TWILIO_PHONE_NUMBER: ${TWILIO_PHONE_NUMBER:-+19492391845}\n ports:\n - \"0.0.0.0:${BETTY_OPERATOR_HOST_PORT:-18120}:8000\"\n volumes:\n - betty_operator_data:/app/data\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 15s\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n n8n:\n image: n8nio/n8n:latest\n container_name: betty-n8n\n depends_on:\n postgres:\n condition: service_healthy\n ollama:\n condition: service_healthy\n ollama-wrapper:\n condition: service_healthy\n betty-memory:\n condition: service_healthy\n betty-actions:\n condition: service_healthy\n betty-lookup:\n condition: service_healthy\n betty-operator:\n condition: service_healthy\n environment:\n N8N_HOST: ${N8N_HOST:-localhost}\n N8N_PORT: 5678\n N8N_PROTOCOL: ${N8N_PROTOCOL:-http}\n WEBHOOK_URL: ${WEBHOOK_URL:-http://localhost:15678/}\n GENERIC_TIMEZONE: ${GENERIC_TIMEZONE:-America/Los_Angeles}\n N8N_ENCRYPTION_KEY: ${N8N_ENCRYPTION_KEY:?set N8N_ENCRYPTION_KEY in .env}\n N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS: \"true\"\n N8N_DIAGNOSTICS_ENABLED: \"false\"\n N8N_PERSONALIZATION_ENABLED: \"false\"\n N8N_SECURE_COOKIE: \"false\"\n N8N_BASIC_AUTH_ACTIVE: ${N8N_BASIC_AUTH_ACTIVE:-true}\n N8N_BASIC_AUTH_USER: ${N8N_BASIC_AUTH_USER:?set N8N_BASIC_AUTH_USER in .env}\n N8N_BASIC_AUTH_PASSWORD: ${N8N_BASIC_AUTH_PASSWORD:?set N8N_BASIC_AUTH_PASSWORD in .env}\n DB_TYPE: postgresdb\n DB_POSTGRESDB_HOST: postgres\n DB_POSTGRESDB_PORT: 5432\n DB_POSTGRESDB_DATABASE: ${POSTGRES_DB:-n8n}\n DB_POSTGRESDB_USER: ${POSTGRES_USER:-n8n}\n DB_POSTGRESDB_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n OLLAMA_WRAPPER_URL: http://ollama-wrapper:8000\n BETTY_MEMORY_URL: http://betty-memory:8000\n BETTY_ACTIONS_URL: http://betty-actions:8000\n BETTY_LOOKUP_URL: http://betty-lookup:8000\n BETTY_OPERATOR_URL: http://betty-operator:8000\n ports:\n - \"127.0.0.1:${N8N_HOST_PORT:-15678}:5678\"\n volumes:\n - n8n_data:/home/node/.n8n\n restart: unless-stopped\n security_opt:\n - no-new-privileges:true\n networks:\n - betty-net\n\nvolumes:\n postgres_data:\n chroma_data:\n ollama_data:\n betty_operator_data:\n betty_whisper_data:\n n8n_data:\n\nnetworks:\n betty-net:\n driver: bridge\n"}, "e5cd4cb99bfa583b5fbebb7ce0a64438c7a5525d": {"t": 1785769582.7823634, "n": 20492, "h": "c4c967930f893c80", "c": "name: betty-stack\n\nservices:\n postgres:\n image: postgres:16-alpine\n container_name: betty-postgres\n environment:\n POSTGRES_USER: ${POSTGRES_USER:-n8n}\n POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n POSTGRES_DB: ${POSTGRES_DB:-n8n}\n volumes:\n - postgres_data:/var/lib/postgresql/data\n healthcheck:\n test: [\"CMD-SHELL\", \"pg_isready -U ${POSTGRES_USER:-n8n} -d ${POSTGRES_DB:-n8n}\"]\n interval: 10s\n timeout: 5s\n retries: 10\n start_period: 15s\n restart: unless-stopped\n security_opt:\n - no-new-privileges:true\n networks:\n - betty-net\n\n chroma:\n image: chromadb/chroma:0.5.23\n container_name: betty-chroma\n volumes:\n - chroma_data:/chroma/chroma\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/api/v1/heartbeat', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 15s\n restart: unless-stopped\n security_opt:\n - no-new-privileges:true\n networks:\n - betty-net\n\n ollama:\n image: ollama/ollama:latest\n container_name: betty-ollama\n ports:\n - \"127.0.0.1:${OLLAMA_HOST_PORT:-11435}:11434\"\n volumes:\n - ollama_data:/root/.ollama\n healthcheck:\n test: [\"CMD-SHELL\", \"ollama list >/dev/null 2>&1 || exit 1\"]\n interval: 20s\n timeout: 10s\n retries: 6\n start_period: 30s\n restart: unless-stopped\n security_opt:\n - no-new-privileges:true\n networks:\n - betty-net\n\n ollama-wrapper:\n build:\n context: ./ollama-wrapper\n image: betty/ollama-wrapper:local\n container_name: betty-ollama-wrapper\n depends_on:\n ollama:\n condition: service_healthy\n environment:\n OLLAMA_URL: http://ollama:11434\n API_USERNAME: ${WRAPPER_API_USERNAME:?set WRAPPER_API_USERNAME in .env}\n API_PASSWORD: ${WRAPPER_API_PASSWORD:?set WRAPPER_API_PASSWORD in .env}\n API_BEARER_TOKEN: ${WRAPPER_API_BEARER_TOKEN:-}\n WRAPPER_REQUEST_TIMEOUT_SECONDS: ${WRAPPER_REQUEST_TIMEOUT_SECONDS:-300}\n LOG_LEVEL: ${WRAPPER_LOG_LEVEL:-info}\n ports:\n - \"127.0.0.1:${WRAPPER_HOST_PORT:-18000}:8000\"\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 10s\n read_only: true\n tmpfs:\n - /tmp\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n betty-memory:\n build:\n context: ./services/memory\n image: betty/memory-service:local\n container_name: betty-memory\n depends_on:\n postgres:\n condition: service_healthy\n chroma:\n condition: service_healthy\n ollama:\n condition: service_healthy\n environment:\n BETTY_MEMORY_POSTGRES_HOST: postgres\n BETTY_MEMORY_POSTGRES_PORT: 5432\n BETTY_MEMORY_POSTGRES_DB: ${POSTGRES_DB:-n8n}\n BETTY_MEMORY_POSTGRES_USER: ${POSTGRES_USER:-n8n}\n BETTY_MEMORY_POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n BETTY_MEMORY_CHROMA_HOST: chroma\n BETTY_MEMORY_CHROMA_PORT: 8000\n BETTY_MEMORY_COLLECTION: ${BETTY_MEMORY_COLLECTION:-betty-v1-facts}\n BETTY_MEMORY_OLLAMA_URL: http://ollama:11434\n BETTY_MEMORY_EMBED_MODEL: ${BETTY_MEMORY_EMBED_MODEL:-nomic-embed-text}\n BETTY_MEMORY_SEMANTIC_DISTANCE_THRESHOLD: ${BETTY_MEMORY_SEMANTIC_DISTANCE_THRESHOLD:-0.45}\n BETTY_MEMORY_LOG_LEVEL: ${BETTY_MEMORY_LOG_LEVEL:-info}\n BETTY_MEMORY_DEFAULT_TIMEZONE: ${GENERIC_TIMEZONE:-America/Los_Angeles}\n BETTY_OWNER_NAME: ${BETTY_OWNER_NAME:-Vincent}\n BETTY_HOME_CITY: ${BETTY_HOME_CITY:-Corona Del Mar, CA}\n ports:\n - \"127.0.0.1:${BETTY_MEMORY_HOST_PORT:-18100}:8000\"\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 15s\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n betty-actions:\n build:\n context: ./services/actions\n image: betty/actions-service:local\n container_name: betty-actions\n depends_on:\n postgres:\n condition: service_healthy\n environment:\n BETTY_ACTIONS_POSTGRES_HOST: postgres\n BETTY_ACTIONS_POSTGRES_PORT: 5432\n BETTY_ACTIONS_POSTGRES_DB: ${POSTGRES_DB:-n8n}\n BETTY_ACTIONS_POSTGRES_USER: ${POSTGRES_USER:-n8n}\n BETTY_ACTIONS_POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n BETTY_ACTIONS_DEFAULT_TIMEZONE: ${GENERIC_TIMEZONE:-America/Los_Angeles}\n GOOGLE_CALENDAR_CLIENT_ID: ${GOOGLE_CALENDAR_CLIENT_ID:-}\n GOOGLE_CALENDAR_CLIENT_SECRET: ${GOOGLE_CALENDAR_CLIENT_SECRET:-}\n ports:\n - \"127.0.0.1:${BETTY_ACTIONS_HOST_PORT:-18110}:8000\"\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 15s\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n betty-lookup:\n build:\n context: ./services/lookup\n image: betty/lookup-service:local\n container_name: betty-lookup\n depends_on:\n postgres:\n condition: service_healthy\n ollama-wrapper:\n condition: service_healthy\n environment:\n BETTY_LOOKUP_POSTGRES_HOST: postgres\n BETTY_LOOKUP_POSTGRES_PORT: 5432\n BETTY_LOOKUP_POSTGRES_DB: ${POSTGRES_DB:-n8n}\n BETTY_LOOKUP_POSTGRES_USER: ${POSTGRES_USER:-n8n}\n BETTY_LOOKUP_POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n BETTY_LOOKUP_DEFAULT_TIMEZONE: ${GENERIC_TIMEZONE:-America/Los_Angeles}\n BETTY_OWNER_NAME: ${BETTY_OWNER_NAME:-Vincent}\n BETTY_HOME_CITY: ${BETTY_HOME_CITY:-Corona Del Mar, CA}\n BRAVE_ENABLED: ${BRAVE_ENABLED:-false}\n SEARXNG_ENABLED: ${SEARXNG_ENABLED:-true}\n SEARXNG_BASE_URL: ${SEARXNG_BASE_URL:-http://host.docker.internal:8080}\n BETTY_COST_DAILY_CAP_USD: ${BETTY_COST_DAILY_CAP_USD:-5}\n BETTY_COST_MONTHLY_CAP_USD: ${BETTY_COST_MONTHLY_CAP_USD:-50}\n BETTY_COST_WARNING_RATIO: ${BETTY_COST_WARNING_RATIO:-0.8}\n BRAVE_SEARCH_API_KEY: ${BRAVE_SEARCH_API_KEY:-}\n BETTY_LOOKUP_WRAPPER_URL: http://ollama-wrapper:8000/v1/chat/completions\n BETTY_LOOKUP_WRAPPER_USERNAME: ${WRAPPER_API_USERNAME:?set WRAPPER_API_USERNAME in .env}\n BETTY_LOOKUP_WRAPPER_PASSWORD: ${WRAPPER_API_PASSWORD:?set WRAPPER_API_PASSWORD in .env}\n BETTY_LOOKUP_WRAPPER_BEARER_TOKEN: ${WRAPPER_API_BEARER_TOKEN:-}\n BETTY_LOOKUP_MODEL: ${BETTY_LOOKUP_MODEL:-}\n BETTY_LOOKUP_WRAPPER_TIMEOUT_SECONDS: ${WRAPPER_REQUEST_TIMEOUT_SECONDS:-120}\n ports:\n - \"127.0.0.1:${BETTY_LOOKUP_HOST_PORT:-18130}:8000\"\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 15s\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n betty-brain:\n build:\n context: ./services/brain\n image: betty/brain-service:local\n container_name: betty-brain\n depends_on:\n postgres:\n condition: service_healthy\n betty-memory:\n condition: service_healthy\n betty-lookup:\n condition: service_healthy\n environment:\n BETTY_BRAIN_POSTGRES_HOST: postgres\n BETTY_BRAIN_POSTGRES_PORT: 5432\n BETTY_BRAIN_POSTGRES_DB: ${POSTGRES_DB:-n8n}\n BETTY_BRAIN_POSTGRES_USER: ${POSTGRES_USER:-n8n}\n BETTY_BRAIN_POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n BETTY_BRAIN_MODEL: ${BETTY_BRAIN_MODEL:-claude-haiku-4-5}\n BETTY_BRAIN_MAX_TOKENS: ${BETTY_BRAIN_MAX_TOKENS:-2000}\n BETTY_BRAIN_MAX_AGENT_ITERATIONS: ${BETTY_BRAIN_MAX_AGENT_ITERATIONS:-8}\n BETTY_BRAIN_MEMORY_URL: http://betty-memory:8000\n BETTY_BRAIN_LOOKUP_URL: http://betty-lookup:8000\n BETTY_BRAIN_OPERATOR_URL: http://betty-operator:8000\n BETTY_BRAIN_ACTIONS_URL: http://betty-actions:8000\n BETTY_BRAIN_TOOL_HTTP_TIMEOUT: ${BETTY_BRAIN_TOOL_HTTP_TIMEOUT:-30}\n BETTY_BRAIN_LOOKUP_HTTP_TIMEOUT: ${BETTY_BRAIN_LOOKUP_HTTP_TIMEOUT:-60}\n BETTY_OWNER_EMAIL: ${BETTY_OWNER_EMAIL:-vincentthinks@gmail.com}\n BETTY_SLACK_BOT_TOKEN: ${BETTY_SLACK_BOT_TOKEN:-}\n BETTY_SLACK_DEFAULT_DELEGATION_CHANNEL: ${BETTY_SLACK_DEFAULT_DELEGATION_CHANNEL:-#betty-station}\n ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:?set ANTHROPIC_API_KEY in .env}\n BETTY_COST_DAILY_CAP_USD: ${BETTY_COST_DAILY_CAP_USD:-1}\n BETTY_COST_MONTHLY_CAP_USD: ${BETTY_COST_MONTHLY_CAP_USD:-30}\n BETTY_COST_WARNING_RATIO: ${BETTY_COST_WARNING_RATIO:-0.8}\n BETTY_OWNER_NAME: ${BETTY_OWNER_NAME:-Vincent}\n BETTY_HOME_CITY: ${BETTY_HOME_CITY:-Rancho Santa Margarita, CA}\n ports:\n - \"127.0.0.1:${BETTY_BRAIN_HOST_PORT:-18140}:8000\"\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 15s\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n betty-daemon:\n build:\n context: ./services/betty-slack-daemon\n image: betty/betty-slack-daemon:local\n container_name: betty-daemon\n depends_on:\n betty-brain:\n condition: service_healthy\n betty-memory:\n condition: service_healthy\n environment:\n BETTY_SLACK_APP_TOKEN: ${BETTY_SLACK_APP_TOKEN:?set BETTY_SLACK_APP_TOKEN in .env after creating Betty's Slack app}\n BETTY_SLACK_BOT_TOKEN: ${BETTY_SLACK_BOT_TOKEN:?set BETTY_SLACK_BOT_TOKEN in .env after creating Betty's Slack app}\n BETTY_BRAIN_URL: http://betty-brain:8000\n BETTY_BRAIN_TIMEOUT_SECONDS: ${BETTY_BRAIN_TIMEOUT_SECONDS:-60}\n BETTY_DAEMON_MEMORY_URL: http://betty-memory:8000\n BETTY_DAEMON_MEMORY_TIMEOUT_SECONDS: ${BETTY_DAEMON_MEMORY_TIMEOUT_SECONDS:-15}\n BETTY_OWNER_SLACK_USER_ID: ${BETTY_OWNER_SLACK_USER_ID:-}\n BETTY_DAEMON_REMINDER_POLL_INTERVAL_SECONDS: ${BETTY_DAEMON_REMINDER_POLL_INTERVAL_SECONDS:-60}\n BETTY_DAEMON_REMINDER_LOOKAHEAD_SECONDS: ${BETTY_DAEMON_REMINDER_LOOKAHEAD_SECONDS:-60}\n BETTY_DAEMON_DATA_DIR: /data\n BETTY_DAEMON_THREAD_KEEP_MSGS: ${BETTY_DAEMON_THREAD_KEEP_MSGS:-20}\n volumes:\n - ./data/betty-daemon:/data\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n betty-operator:\n build:\n context: ./services/operator\n image: betty/operator-service:local\n container_name: betty-operator\n depends_on:\n postgres:\n condition: service_healthy\n betty-memory:\n condition: service_healthy\n betty-actions:\n condition: service_healthy\n betty-lookup:\n condition: service_healthy\n environment:\n BETTY_OPERATOR_POSTGRES_HOST: postgres\n BETTY_OPERATOR_POSTGRES_PORT: 5432\n BETTY_OPERATOR_POSTGRES_DB: ${POSTGRES_DB:-n8n}\n BETTY_OPERATOR_POSTGRES_USER: ${POSTGRES_USER:-n8n}\n BETTY_OPERATOR_POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n BETTY_OPERATOR_MEMORY_URL: http://betty-memory:8000\n BETTY_OPERATOR_ACTIONS_URL: http://betty-actions:8000\n BETTY_OPERATOR_LOOKUP_URL: http://betty-lookup:8000\n BETTY_OPERATOR_BRAIN_URL: http://betty-brain:8000\n BETTY_OPERATOR_BRAIN_TIMEOUT_SECONDS: ${BETTY_OPERATOR_BRAIN_TIMEOUT_SECONDS:-60}\n BETTY_OPERATOR_BRAIN_DEFAULT_ENABLED: ${BETTY_OPERATOR_BRAIN_DEFAULT_ENABLED:-true}\n BETTY_OPERATOR_WRAPPER_URL: http://ollama-wrapper:8000/v1/chat/completions\n BETTY_OPERATOR_WRAPPER_USERNAME: ${WRAPPER_API_USERNAME:?set WRAPPER_API_USERNAME in .env}\n BETTY_OPERATOR_WRAPPER_PASSWORD: ${WRAPPER_API_PASSWORD:?set WRAPPER_API_PASSWORD in .env}\n BETTY_OPERATOR_WRAPPER_BEARER_TOKEN: ${WRAPPER_API_BEARER_TOKEN:-}\n BETTY_OPERATOR_WRAPPER_TIMEOUT_SECONDS: ${WRAPPER_REQUEST_TIMEOUT_SECONDS:-120}\n BETTY_OPERATOR_DEFAULT_TIMEZONE: ${GENERIC_TIMEZONE:-America/Los_Angeles}\n BETTY_PUBLIC_BASE_URL: ${BETTY_PUBLIC_BASE_URL:-https://YOUR_PUBLIC_URL}\n BETTY_OWNER_PHONE: ${BETTY_OWNER_PHONE:-}\n BETTY_OWNER_NAME: ${BETTY_OWNER_NAME:-Vincent}\n BETTY_HOME_CITY: ${BETTY_HOME_CITY:-Corona Del Mar, CA}\n BETTY_OPERATOR_STT_PROVIDER: ${BETTY_OPERATOR_STT_PROVIDER:-whisper_local}\n BETTY_OPERATOR_WHISPER_MODEL: ${BETTY_OPERATOR_WHISPER_MODEL:-base.en}\n BETTY_OPERATOR_WHISPER_DEVICE: ${BETTY_OPERATOR_WHISPER_DEVICE:-cpu}\n BETTY_OPERATOR_WHISPER_COMPUTE_TYPE: ${BETTY_OPERATOR_WHISPER_COMPUTE_TYPE:-int8}\n BETTY_OPERATOR_WHISPER_DATA_DIR: ${BETTY_OPERATOR_WHISPER_DATA_DIR:-/app/data/whisper}\n BETTY_MODEL_MODE: ${BETTY_MODEL_MODE:-default}\n BETTY_DEFAULT_CHAT_PROVIDER: ${BETTY_DEFAULT_CHAT_PROVIDER:-auto}\n BETTY_DEFAULT_CHAT_MODEL: ${BETTY_DEFAULT_CHAT_MODEL:-deepseek-chat}\n BETTY_DEFAULT_LOCAL_CHAT_MODEL: ${BETTY_DEFAULT_LOCAL_CHAT_MODEL:-llama3.1:8b}\n BETTY_MODEL_DOLPHIN: ${BETTY_MODEL_DOLPHIN:-dolphin-llama3:70b}\n BETTY_MODEL_MEDICAL: ${BETTY_MODEL_MEDICAL:-medllama2}\n BETTY_MODEL_MUSIC: ${BETTY_MODEL_MUSIC:-llamusic/llamusic}\n BETTY_MODEL_IMAGE: ${BETTY_MODEL_IMAGE:-llava:13b}\n # Slice 8: photo/scan vision. DGX direct (bypasses ollama-wrapper since wrapper\n # talks to local betty-ollama which has no LLaVA). Set to ollama-wrapper URL\n # if DGX is unreachable and you've pulled llava:13b locally.\n BETTY_VISION_OLLAMA_URL: ${BETTY_VISION_OLLAMA_URL:-http://192.168.68.79:11434}\n BETTY_VISION_TIMEOUT_SECONDS: ${BETTY_VISION_TIMEOUT_SECONDS:-90}\n # Cloud vision tier (Photo+ button → Haiku 4.5 vision via Anthropic API).\n ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-}\n BETTY_VISION_CLOUD_MODEL: ${BETTY_VISION_CLOUD_MODEL:-claude-haiku-4-5}\n BETTY_VISION_CLOUD_INPUT_COST_PER_1M_USD: ${BETTY_VISION_CLOUD_INPUT_COST_PER_1M_USD:-1.0}\n BETTY_VISION_CLOUD_OUTPUT_COST_PER_1M_USD: ${BETTY_VISION_CLOUD_OUTPUT_COST_PER_1M_USD:-5.0}\n BETTY_MODEL_WRITE: ${BETTY_MODEL_WRITE:-HammerAI/mythomax-l2}\n BETTY_MODEL_CODE: ${BETTY_MODEL_CODE:-qwen2.5-coder:32b}\n BETTY_MODEL_RESEARCH: ${BETTY_MODEL_RESEARCH:-nemotron}\n BETTY_MODEL_FAST: ${BETTY_MODEL_FAST:-gemma2:27b}\n DEEPSEEK_API_KEY: ${DEEPSEEK_API_KEY:-}\n DEEPSEEK_BASE_URL: ${DEEPSEEK_BASE_URL:-https://api.deepseek.com}\n BETTY_DEEPSEEK_MODEL: ${BETTY_DEEPSEEK_MODEL:-deepseek-chat}\n BETTY_DEEPSEEK_REASONER_MODEL: ${BETTY_DEEPSEEK_REASONER_MODEL:-deepseek-reasoner}\n BETTY_DEEPSEEK_TIMEOUT_SECONDS: ${BETTY_DEEPSEEK_TIMEOUT_SECONDS:-120}\n BETTY_DEEPSEEK_CHAT_INPUT_COST_PER_1M_USD: ${BETTY_DEEPSEEK_CHAT_INPUT_COST_PER_1M_USD:-0.27}\n BETTY_DEEPSEEK_CHAT_OUTPUT_COST_PER_1M_USD: ${BETTY_DEEPSEEK_CHAT_OUTPUT_COST_PER_1M_USD:-1.10}\n BETTY_DEEPSEEK_REASONER_INPUT_COST_PER_1M_USD: ${BETTY_DEEPSEEK_REASONER_INPUT_COST_PER_1M_USD:-0.55}\n BETTY_DEEPSEEK_REASONER_OUTPUT_COST_PER_1M_USD: ${BETTY_DEEPSEEK_REASONER_OUTPUT_COST_PER_1M_USD:-2.19}\n KOKORO_ENABLED: ${KOKORO_ENABLED:-true}\n KOKORO_DEFAULT_VOICE: ${KOKORO_DEFAULT_VOICE:-af_bella}\n BETTY_OPERATOR_KOKORO_SPEED: ${BETTY_OPERATOR_KOKORO_SPEED:-1.0}\n BETTY_OPERATOR_KOKORO_DATA_DIR: ${BETTY_OPERATOR_KOKORO_DATA_DIR:-/app/data/kokoro}\n ELEVENLABS_ENABLED: ${ELEVENLABS_ENABLED:-true}\n TWILIO_ENABLED: ${TWILIO_ENABLED:-true}\n VAPI_ENABLED: ${VAPI_ENABLED:-true}\n BETTY_COST_DAILY_CAP_USD: ${BETTY_COST_DAILY_CAP_USD:-5}\n BETTY_COST_MONTHLY_CAP_USD: ${BETTY_COST_MONTHLY_CAP_USD:-50}\n BETTY_COST_WARNING_RATIO: ${BETTY_COST_WARNING_RATIO:-0.8}\n BETTY_ELEVENLABS_COST_PER_1K_CHARS_USD: ${BETTY_ELEVENLABS_COST_PER_1K_CHARS_USD:-0.30}\n BETTY_TWILIO_SMS_COST_USD: ${BETTY_TWILIO_SMS_COST_USD:-0.008}\n BETTY_VAPI_COST_PER_MINUTE_USD: ${BETTY_VAPI_COST_PER_MINUTE_USD:-0.075}\n ELEVENLABS_API_KEY: ${ELEVENLABS_API_KEY:-}\n ELEVENLABS_VOICE_ID: ${ELEVENLABS_VOICE_ID:-4tRn1lSkEn13EVTuqb0g}\n ELEVENLABS_MODEL_ID: ${ELEVENLABS_MODEL_ID:-eleven_turbo_v2_5}\n PIPER_VOICE_MODEL: ${PIPER_VOICE_MODEL:-en_US-lessac-medium}\n BETTY_OPERATOR_PIPER_DATA_DIR: ${BETTY_OPERATOR_PIPER_DATA_DIR:-/app/data/piper}\n GOOGLE_CALENDAR_CLIENT_ID: ${GOOGLE_CALENDAR_CLIENT_ID:-}\n GOOGLE_CALENDAR_CLIENT_SECRET: ${GOOGLE_CALENDAR_CLIENT_SECRET:-}\n GOOGLE_CALENDAR_OAUTH_REDIRECT_URI: ${GOOGLE_CALENDAR_OAUTH_REDIRECT_URI:-}\n BETTY_OPERATOR_RATE_LIMIT_WINDOW_SECONDS: ${BETTY_OPERATOR_RATE_LIMIT_WINDOW_SECONDS:-60}\n BETTY_OPERATOR_ELEVENLABS_RATE_LIMIT: ${BETTY_OPERATOR_ELEVENLABS_RATE_LIMIT:-20}\n BETTY_OPERATOR_TWILIO_RATE_LIMIT: ${BETTY_OPERATOR_TWILIO_RATE_LIMIT:-20}\n VAPI_API_KEY: ${VAPI_API_KEY:-}\n VAPI_ASSISTANT_ID: ${VAPI_ASSISTANT_ID:-}\n VAPI_WEBHOOK_SECRET: ${VAPI_WEBHOOK_SECRET:-}\n TWILIO_ACCOUNT_SID: ${TWILIO_ACCOUNT_SID:-}\n TWILIO_AUTH_TOKEN: ${TWILIO_AUTH_TOKEN:-}\n TWILIO_PHONE_NUMBER: ${TWILIO_PHONE_NUMBER:-+19492391845}\n ports:\n - \"0.0.0.0:${BETTY_OPERATOR_HOST_PORT:-18120}:8000\"\n volumes:\n - betty_operator_data:/app/data\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 15s\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n n8n:\n image: n8nio/n8n:latest\n container_name: betty-n8n\n depends_on:\n postgres:\n condition: service_healthy\n ollama:\n condition: service_healthy\n ollama-wrapper:\n condition: service_healthy\n betty-memory:\n condition: service_healthy\n betty-actions:\n condition: service_healthy\n betty-lookup:\n condition: service_healthy\n betty-operator:\n condition: service_healthy\n environment:\n N8N_HOST: ${N8N_HOST:-localhost}\n N8N_PORT: 5678\n N8N_PROTOCOL: ${N8N_PROTOCOL:-http}\n WEBHOOK_URL: ${WEBHOOK_URL:-http://localhost:15678/}\n GENERIC_TIMEZONE: ${GENERIC_TIMEZONE:-America/Los_Angeles}\n N8N_ENCRYPTION_KEY: ${N8N_ENCRYPTION_KEY:?set N8N_ENCRYPTION_KEY in .env}\n N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS: \"true\"\n N8N_DIAGNOSTICS_ENABLED: \"false\"\n N8N_PERSONALIZATION_ENABLED: \"false\"\n N8N_SECURE_COOKIE: \"false\"\n N8N_BASIC_AUTH_ACTIVE: ${N8N_BASIC_AUTH_ACTIVE:-true}\n N8N_BASIC_AUTH_USER: ${N8N_BASIC_AUTH_USER:?set N8N_BASIC_AUTH_USER in .env}\n N8N_BASIC_AUTH_PASSWORD: ${N8N_BASIC_AUTH_PASSWORD:?set N8N_BASIC_AUTH_PASSWORD in .env}\n DB_TYPE: postgresdb\n DB_POSTGRESDB_HOST: postgres\n DB_POSTGRESDB_PORT: 5432\n DB_POSTGRESDB_DATABASE: ${POSTGRES_DB:-n8n}\n DB_POSTGRESDB_USER: ${POSTGRES_USER:-n8n}\n DB_POSTGRESDB_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n OLLAMA_WRAPPER_URL: http://ollama-wrapper:8000\n BETTY_MEMORY_URL: http://betty-memory:8000\n BETTY_ACTIONS_URL: http://betty-actions:8000\n BETTY_LOOKUP_URL: http://betty-lookup:8000\n BETTY_OPERATOR_URL: http://betty-operator:8000\n ports:\n - \"127.0.0.1:${N8N_HOST_PORT:-15678}:5678\"\n volumes:\n - n8n_data:/home/node/.n8n\n restart: unless-stopped\n security_opt:\n - no-new-privileges:true\n networks:\n - betty-net\n\nvolumes:\n postgres_data:\n chroma_data:\n ollama_data:\n betty_operator_data:\n n8n_data:\n\nnetworks:\n betty-net:\n driver: bridge\n"}, "54db37acbfa0f7b96ff8ad55f6ada555a61b3714": {"t": 1785769584.22096, "n": 20435, "h": "f1eb463f23a6c2f8", "c": "name: betty-stack\n\nservices:\n postgres:\n image: postgres:16-alpine\n container_name: betty-postgres\n environment:\n POSTGRES_USER: ${POSTGRES_USER:-n8n}\n POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n POSTGRES_DB: ${POSTGRES_DB:-n8n}\n volumes:\n - postgres_data:/var/lib/postgresql/data\n healthcheck:\n test: [\"CMD-SHELL\", \"pg_isready -U ${POSTGRES_USER:-n8n} -d ${POSTGRES_DB:-n8n}\"]\n interval: 10s\n timeout: 5s\n retries: 10\n start_period: 15s\n restart: unless-stopped\n security_opt:\n - no-new-privileges:true\n networks:\n - betty-net\n\n chroma:\n image: chromadb/chroma:0.5.23\n container_name: betty-chroma\n volumes:\n - chroma_data:/chroma/chroma\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/api/v1/heartbeat', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 15s\n restart: unless-stopped\n security_opt:\n - no-new-privileges:true\n networks:\n - betty-net\n\n ollama:\n image: ollama/ollama:latest\n container_name: betty-ollama\n ports:\n - \"127.0.0.1:${OLLAMA_HOST_PORT:-11435}:11434\"\n volumes:\n - ollama_data:/root/.ollama\n healthcheck:\n test: [\"CMD-SHELL\", \"ollama list >/dev/null 2>&1 || exit 1\"]\n interval: 20s\n timeout: 10s\n retries: 6\n start_period: 30s\n restart: unless-stopped\n security_opt:\n - no-new-privileges:true\n networks:\n - betty-net\n\n ollama-wrapper:\n build:\n context: ./ollama-wrapper\n image: betty/ollama-wrapper:local\n container_name: betty-ollama-wrapper\n depends_on:\n ollama:\n condition: service_healthy\n environment:\n OLLAMA_URL: http://ollama:11434\n API_USERNAME: ${WRAPPER_API_USERNAME:?set WRAPPER_API_USERNAME in .env}\n API_PASSWORD: ${WRAPPER_API_PASSWORD:?set WRAPPER_API_PASSWORD in .env}\n API_BEARER_TOKEN: ${WRAPPER_API_BEARER_TOKEN:-}\n WRAPPER_REQUEST_TIMEOUT_SECONDS: ${WRAPPER_REQUEST_TIMEOUT_SECONDS:-300}\n LOG_LEVEL: ${WRAPPER_LOG_LEVEL:-info}\n ports:\n - \"127.0.0.1:${WRAPPER_HOST_PORT:-18000}:8000\"\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 10s\n read_only: true\n tmpfs:\n - /tmp\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n betty-memory:\n build:\n context: ./services/memory\n image: betty/memory-service:local\n container_name: betty-memory\n depends_on:\n postgres:\n condition: service_healthy\n chroma:\n condition: service_healthy\n ollama:\n condition: service_healthy\n environment:\n BETTY_MEMORY_POSTGRES_HOST: postgres\n BETTY_MEMORY_POSTGRES_PORT: 5432\n BETTY_MEMORY_POSTGRES_DB: ${POSTGRES_DB:-n8n}\n BETTY_MEMORY_POSTGRES_USER: ${POSTGRES_USER:-n8n}\n BETTY_MEMORY_POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n BETTY_MEMORY_CHROMA_HOST: chroma\n BETTY_MEMORY_CHROMA_PORT: 8000\n BETTY_MEMORY_COLLECTION: ${BETTY_MEMORY_COLLECTION:-betty-v1-facts}\n BETTY_MEMORY_OLLAMA_URL: http://ollama:11434\n BETTY_MEMORY_EMBED_MODEL: ${BETTY_MEMORY_EMBED_MODEL:-nomic-embed-text}\n BETTY_MEMORY_SEMANTIC_DISTANCE_THRESHOLD: ${BETTY_MEMORY_SEMANTIC_DISTANCE_THRESHOLD:-0.45}\n BETTY_MEMORY_LOG_LEVEL: ${BETTY_MEMORY_LOG_LEVEL:-info}\n BETTY_MEMORY_DEFAULT_TIMEZONE: ${GENERIC_TIMEZONE:-America/Los_Angeles}\n BETTY_OWNER_NAME: ${BETTY_OWNER_NAME:-Vincent}\n BETTY_HOME_CITY: ${BETTY_HOME_CITY:-Corona Del Mar, CA}\n ports:\n - \"127.0.0.1:${BETTY_MEMORY_HOST_PORT:-18100}:8000\"\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 15s\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n betty-actions:\n build:\n context: ./services/actions\n image: betty/actions-service:local\n container_name: betty-actions\n depends_on:\n postgres:\n condition: service_healthy\n environment:\n BETTY_ACTIONS_POSTGRES_HOST: postgres\n BETTY_ACTIONS_POSTGRES_PORT: 5432\n BETTY_ACTIONS_POSTGRES_DB: ${POSTGRES_DB:-n8n}\n BETTY_ACTIONS_POSTGRES_USER: ${POSTGRES_USER:-n8n}\n BETTY_ACTIONS_POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n BETTY_ACTIONS_DEFAULT_TIMEZONE: ${GENERIC_TIMEZONE:-America/Los_Angeles}\n GOOGLE_CALENDAR_CLIENT_ID: ${GOOGLE_CALENDAR_CLIENT_ID:-}\n GOOGLE_CALENDAR_CLIENT_SECRET: ${GOOGLE_CALENDAR_CLIENT_SECRET:-}\n ports:\n - \"127.0.0.1:${BETTY_ACTIONS_HOST_PORT:-18110}:8000\"\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 15s\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n betty-lookup:\n build:\n context: ./services/lookup\n image: betty/lookup-service:local\n container_name: betty-lookup\n depends_on:\n postgres:\n condition: service_healthy\n ollama-wrapper:\n condition: service_healthy\n environment:\n BETTY_LOOKUP_POSTGRES_HOST: postgres\n BETTY_LOOKUP_POSTGRES_PORT: 5432\n BETTY_LOOKUP_POSTGRES_DB: ${POSTGRES_DB:-n8n}\n BETTY_LOOKUP_POSTGRES_USER: ${POSTGRES_USER:-n8n}\n BETTY_LOOKUP_POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n BETTY_LOOKUP_DEFAULT_TIMEZONE: ${GENERIC_TIMEZONE:-America/Los_Angeles}\n BETTY_OWNER_NAME: ${BETTY_OWNER_NAME:-Vincent}\n BETTY_HOME_CITY: ${BETTY_HOME_CITY:-Corona Del Mar, CA}\n BRAVE_ENABLED: ${BRAVE_ENABLED:-false}\n SEARXNG_ENABLED: ${SEARXNG_ENABLED:-true}\n SEARXNG_BASE_URL: ${SEARXNG_BASE_URL:-http://host.docker.internal:8080}\n BETTY_COST_DAILY_CAP_USD: ${BETTY_COST_DAILY_CAP_USD:-5}\n BETTY_COST_MONTHLY_CAP_USD: ${BETTY_COST_MONTHLY_CAP_USD:-50}\n BETTY_COST_WARNING_RATIO: ${BETTY_COST_WARNING_RATIO:-0.8}\n BRAVE_SEARCH_API_KEY: ${BRAVE_SEARCH_API_KEY:-}\n BETTY_LOOKUP_WRAPPER_URL: http://ollama-wrapper:8000/v1/chat/completions\n BETTY_LOOKUP_WRAPPER_USERNAME: ${WRAPPER_API_USERNAME:?set WRAPPER_API_USERNAME in .env}\n BETTY_LOOKUP_WRAPPER_PASSWORD: ${WRAPPER_API_PASSWORD:?set WRAPPER_API_PASSWORD in .env}\n BETTY_LOOKUP_WRAPPER_BEARER_TOKEN: ${WRAPPER_API_BEARER_TOKEN:-}\n BETTY_LOOKUP_MODEL: ${BETTY_LOOKUP_MODEL:-}\n BETTY_LOOKUP_WRAPPER_TIMEOUT_SECONDS: ${WRAPPER_REQUEST_TIMEOUT_SECONDS:-120}\n ports:\n - \"127.0.0.1:${BETTY_LOOKUP_HOST_PORT:-18130}:8000\"\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 15s\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n betty-brain:\n build:\n context: ./services/brain\n image: betty/brain-service:local\n container_name: betty-brain\n depends_on:\n postgres:\n condition: service_healthy\n betty-memory:\n condition: service_healthy\n betty-lookup:\n condition: service_healthy\n environment:\n BETTY_BRAIN_POSTGRES_HOST: postgres\n BETTY_BRAIN_POSTGRES_PORT: 5432\n BETTY_BRAIN_POSTGRES_DB: ${POSTGRES_DB:-n8n}\n BETTY_BRAIN_POSTGRES_USER: ${POSTGRES_USER:-n8n}\n BETTY_BRAIN_POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n BETTY_BRAIN_MODEL: ${BETTY_BRAIN_MODEL:-claude-haiku-4-5}\n BETTY_BRAIN_MAX_TOKENS: ${BETTY_BRAIN_MAX_TOKENS:-2000}\n BETTY_BRAIN_MAX_AGENT_ITERATIONS: ${BETTY_BRAIN_MAX_AGENT_ITERATIONS:-8}\n BETTY_BRAIN_MEMORY_URL: http://betty-memory:8000\n BETTY_BRAIN_LOOKUP_URL: http://betty-lookup:8000\n BETTY_BRAIN_OPERATOR_URL: http://betty-operator:8000\n BETTY_BRAIN_TOOL_HTTP_TIMEOUT: ${BETTY_BRAIN_TOOL_HTTP_TIMEOUT:-30}\n BETTY_BRAIN_LOOKUP_HTTP_TIMEOUT: ${BETTY_BRAIN_LOOKUP_HTTP_TIMEOUT:-60}\n BETTY_OWNER_EMAIL: ${BETTY_OWNER_EMAIL:-vincentthinks@gmail.com}\n BETTY_SLACK_BOT_TOKEN: ${BETTY_SLACK_BOT_TOKEN:-}\n BETTY_SLACK_DEFAULT_DELEGATION_CHANNEL: ${BETTY_SLACK_DEFAULT_DELEGATION_CHANNEL:-#betty-station}\n ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:?set ANTHROPIC_API_KEY in .env}\n BETTY_COST_DAILY_CAP_USD: ${BETTY_COST_DAILY_CAP_USD:-1}\n BETTY_COST_MONTHLY_CAP_USD: ${BETTY_COST_MONTHLY_CAP_USD:-30}\n BETTY_COST_WARNING_RATIO: ${BETTY_COST_WARNING_RATIO:-0.8}\n BETTY_OWNER_NAME: ${BETTY_OWNER_NAME:-Vincent}\n BETTY_HOME_CITY: ${BETTY_HOME_CITY:-Rancho Santa Margarita, CA}\n ports:\n - \"127.0.0.1:${BETTY_BRAIN_HOST_PORT:-18140}:8000\"\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 15s\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n betty-daemon:\n build:\n context: ./services/betty-slack-daemon\n image: betty/betty-slack-daemon:local\n container_name: betty-daemon\n depends_on:\n betty-brain:\n condition: service_healthy\n betty-memory:\n condition: service_healthy\n environment:\n BETTY_SLACK_APP_TOKEN: ${BETTY_SLACK_APP_TOKEN:?set BETTY_SLACK_APP_TOKEN in .env after creating Betty's Slack app}\n BETTY_SLACK_BOT_TOKEN: ${BETTY_SLACK_BOT_TOKEN:?set BETTY_SLACK_BOT_TOKEN in .env after creating Betty's Slack app}\n BETTY_BRAIN_URL: http://betty-brain:8000\n BETTY_BRAIN_TIMEOUT_SECONDS: ${BETTY_BRAIN_TIMEOUT_SECONDS:-60}\n BETTY_DAEMON_MEMORY_URL: http://betty-memory:8000\n BETTY_DAEMON_MEMORY_TIMEOUT_SECONDS: ${BETTY_DAEMON_MEMORY_TIMEOUT_SECONDS:-15}\n BETTY_OWNER_SLACK_USER_ID: ${BETTY_OWNER_SLACK_USER_ID:-}\n BETTY_DAEMON_REMINDER_POLL_INTERVAL_SECONDS: ${BETTY_DAEMON_REMINDER_POLL_INTERVAL_SECONDS:-60}\n BETTY_DAEMON_REMINDER_LOOKAHEAD_SECONDS: ${BETTY_DAEMON_REMINDER_LOOKAHEAD_SECONDS:-60}\n BETTY_DAEMON_DATA_DIR: /data\n BETTY_DAEMON_THREAD_KEEP_MSGS: ${BETTY_DAEMON_THREAD_KEEP_MSGS:-20}\n volumes:\n - ./data/betty-daemon:/data\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n betty-operator:\n build:\n context: ./services/operator\n image: betty/operator-service:local\n container_name: betty-operator\n depends_on:\n postgres:\n condition: service_healthy\n betty-memory:\n condition: service_healthy\n betty-actions:\n condition: service_healthy\n betty-lookup:\n condition: service_healthy\n environment:\n BETTY_OPERATOR_POSTGRES_HOST: postgres\n BETTY_OPERATOR_POSTGRES_PORT: 5432\n BETTY_OPERATOR_POSTGRES_DB: ${POSTGRES_DB:-n8n}\n BETTY_OPERATOR_POSTGRES_USER: ${POSTGRES_USER:-n8n}\n BETTY_OPERATOR_POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n BETTY_OPERATOR_MEMORY_URL: http://betty-memory:8000\n BETTY_OPERATOR_ACTIONS_URL: http://betty-actions:8000\n BETTY_OPERATOR_LOOKUP_URL: http://betty-lookup:8000\n BETTY_OPERATOR_BRAIN_URL: http://betty-brain:8000\n BETTY_OPERATOR_BRAIN_TIMEOUT_SECONDS: ${BETTY_OPERATOR_BRAIN_TIMEOUT_SECONDS:-60}\n BETTY_OPERATOR_BRAIN_DEFAULT_ENABLED: ${BETTY_OPERATOR_BRAIN_DEFAULT_ENABLED:-true}\n BETTY_OPERATOR_WRAPPER_URL: http://ollama-wrapper:8000/v1/chat/completions\n BETTY_OPERATOR_WRAPPER_USERNAME: ${WRAPPER_API_USERNAME:?set WRAPPER_API_USERNAME in .env}\n BETTY_OPERATOR_WRAPPER_PASSWORD: ${WRAPPER_API_PASSWORD:?set WRAPPER_API_PASSWORD in .env}\n BETTY_OPERATOR_WRAPPER_BEARER_TOKEN: ${WRAPPER_API_BEARER_TOKEN:-}\n BETTY_OPERATOR_WRAPPER_TIMEOUT_SECONDS: ${WRAPPER_REQUEST_TIMEOUT_SECONDS:-120}\n BETTY_OPERATOR_DEFAULT_TIMEZONE: ${GENERIC_TIMEZONE:-America/Los_Angeles}\n BETTY_PUBLIC_BASE_URL: ${BETTY_PUBLIC_BASE_URL:-https://YOUR_PUBLIC_URL}\n BETTY_OWNER_PHONE: ${BETTY_OWNER_PHONE:-}\n BETTY_OWNER_NAME: ${BETTY_OWNER_NAME:-Vincent}\n BETTY_HOME_CITY: ${BETTY_HOME_CITY:-Corona Del Mar, CA}\n BETTY_OPERATOR_STT_PROVIDER: ${BETTY_OPERATOR_STT_PROVIDER:-whisper_local}\n BETTY_OPERATOR_WHISPER_MODEL: ${BETTY_OPERATOR_WHISPER_MODEL:-base.en}\n BETTY_OPERATOR_WHISPER_DEVICE: ${BETTY_OPERATOR_WHISPER_DEVICE:-cpu}\n BETTY_OPERATOR_WHISPER_COMPUTE_TYPE: ${BETTY_OPERATOR_WHISPER_COMPUTE_TYPE:-int8}\n BETTY_OPERATOR_WHISPER_DATA_DIR: ${BETTY_OPERATOR_WHISPER_DATA_DIR:-/app/data/whisper}\n BETTY_MODEL_MODE: ${BETTY_MODEL_MODE:-default}\n BETTY_DEFAULT_CHAT_PROVIDER: ${BETTY_DEFAULT_CHAT_PROVIDER:-auto}\n BETTY_DEFAULT_CHAT_MODEL: ${BETTY_DEFAULT_CHAT_MODEL:-deepseek-chat}\n BETTY_DEFAULT_LOCAL_CHAT_MODEL: ${BETTY_DEFAULT_LOCAL_CHAT_MODEL:-llama3.1:8b}\n BETTY_MODEL_DOLPHIN: ${BETTY_MODEL_DOLPHIN:-dolphin-llama3:70b}\n BETTY_MODEL_MEDICAL: ${BETTY_MODEL_MEDICAL:-medllama2}\n BETTY_MODEL_MUSIC: ${BETTY_MODEL_MUSIC:-llamusic/llamusic}\n BETTY_MODEL_IMAGE: ${BETTY_MODEL_IMAGE:-llava:13b}\n # Slice 8: photo/scan vision. DGX direct (bypasses ollama-wrapper since wrapper\n # talks to local betty-ollama which has no LLaVA). Set to ollama-wrapper URL\n # if DGX is unreachable and you've pulled llava:13b locally.\n BETTY_VISION_OLLAMA_URL: ${BETTY_VISION_OLLAMA_URL:-http://192.168.68.79:11434}\n BETTY_VISION_TIMEOUT_SECONDS: ${BETTY_VISION_TIMEOUT_SECONDS:-90}\n # Cloud vision tier (Photo+ button → Haiku 4.5 vision via Anthropic API).\n ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-}\n BETTY_VISION_CLOUD_MODEL: ${BETTY_VISION_CLOUD_MODEL:-claude-haiku-4-5}\n BETTY_VISION_CLOUD_INPUT_COST_PER_1M_USD: ${BETTY_VISION_CLOUD_INPUT_COST_PER_1M_USD:-1.0}\n BETTY_VISION_CLOUD_OUTPUT_COST_PER_1M_USD: ${BETTY_VISION_CLOUD_OUTPUT_COST_PER_1M_USD:-5.0}\n BETTY_MODEL_WRITE: ${BETTY_MODEL_WRITE:-HammerAI/mythomax-l2}\n BETTY_MODEL_CODE: ${BETTY_MODEL_CODE:-qwen2.5-coder:32b}\n BETTY_MODEL_RESEARCH: ${BETTY_MODEL_RESEARCH:-nemotron}\n BETTY_MODEL_FAST: ${BETTY_MODEL_FAST:-gemma2:27b}\n DEEPSEEK_API_KEY: ${DEEPSEEK_API_KEY:-}\n DEEPSEEK_BASE_URL: ${DEEPSEEK_BASE_URL:-https://api.deepseek.com}\n BETTY_DEEPSEEK_MODEL: ${BETTY_DEEPSEEK_MODEL:-deepseek-chat}\n BETTY_DEEPSEEK_REASONER_MODEL: ${BETTY_DEEPSEEK_REASONER_MODEL:-deepseek-reasoner}\n BETTY_DEEPSEEK_TIMEOUT_SECONDS: ${BETTY_DEEPSEEK_TIMEOUT_SECONDS:-120}\n BETTY_DEEPSEEK_CHAT_INPUT_COST_PER_1M_USD: ${BETTY_DEEPSEEK_CHAT_INPUT_COST_PER_1M_USD:-0.27}\n BETTY_DEEPSEEK_CHAT_OUTPUT_COST_PER_1M_USD: ${BETTY_DEEPSEEK_CHAT_OUTPUT_COST_PER_1M_USD:-1.10}\n BETTY_DEEPSEEK_REASONER_INPUT_COST_PER_1M_USD: ${BETTY_DEEPSEEK_REASONER_INPUT_COST_PER_1M_USD:-0.55}\n BETTY_DEEPSEEK_REASONER_OUTPUT_COST_PER_1M_USD: ${BETTY_DEEPSEEK_REASONER_OUTPUT_COST_PER_1M_USD:-2.19}\n KOKORO_ENABLED: ${KOKORO_ENABLED:-true}\n KOKORO_DEFAULT_VOICE: ${KOKORO_DEFAULT_VOICE:-af_bella}\n BETTY_OPERATOR_KOKORO_SPEED: ${BETTY_OPERATOR_KOKORO_SPEED:-1.0}\n BETTY_OPERATOR_KOKORO_DATA_DIR: ${BETTY_OPERATOR_KOKORO_DATA_DIR:-/app/data/kokoro}\n ELEVENLABS_ENABLED: ${ELEVENLABS_ENABLED:-true}\n TWILIO_ENABLED: ${TWILIO_ENABLED:-true}\n VAPI_ENABLED: ${VAPI_ENABLED:-true}\n BETTY_COST_DAILY_CAP_USD: ${BETTY_COST_DAILY_CAP_USD:-5}\n BETTY_COST_MONTHLY_CAP_USD: ${BETTY_COST_MONTHLY_CAP_USD:-50}\n BETTY_COST_WARNING_RATIO: ${BETTY_COST_WARNING_RATIO:-0.8}\n BETTY_ELEVENLABS_COST_PER_1K_CHARS_USD: ${BETTY_ELEVENLABS_COST_PER_1K_CHARS_USD:-0.30}\n BETTY_TWILIO_SMS_COST_USD: ${BETTY_TWILIO_SMS_COST_USD:-0.008}\n BETTY_VAPI_COST_PER_MINUTE_USD: ${BETTY_VAPI_COST_PER_MINUTE_USD:-0.075}\n ELEVENLABS_API_KEY: ${ELEVENLABS_API_KEY:-}\n ELEVENLABS_VOICE_ID: ${ELEVENLABS_VOICE_ID:-4tRn1lSkEn13EVTuqb0g}\n ELEVENLABS_MODEL_ID: ${ELEVENLABS_MODEL_ID:-eleven_turbo_v2_5}\n PIPER_VOICE_MODEL: ${PIPER_VOICE_MODEL:-en_US-lessac-medium}\n BETTY_OPERATOR_PIPER_DATA_DIR: ${BETTY_OPERATOR_PIPER_DATA_DIR:-/app/data/piper}\n GOOGLE_CALENDAR_CLIENT_ID: ${GOOGLE_CALENDAR_CLIENT_ID:-}\n GOOGLE_CALENDAR_CLIENT_SECRET: ${GOOGLE_CALENDAR_CLIENT_SECRET:-}\n GOOGLE_CALENDAR_OAUTH_REDIRECT_URI: ${GOOGLE_CALENDAR_OAUTH_REDIRECT_URI:-}\n BETTY_OPERATOR_RATE_LIMIT_WINDOW_SECONDS: ${BETTY_OPERATOR_RATE_LIMIT_WINDOW_SECONDS:-60}\n BETTY_OPERATOR_ELEVENLABS_RATE_LIMIT: ${BETTY_OPERATOR_ELEVENLABS_RATE_LIMIT:-20}\n BETTY_OPERATOR_TWILIO_RATE_LIMIT: ${BETTY_OPERATOR_TWILIO_RATE_LIMIT:-20}\n VAPI_API_KEY: ${VAPI_API_KEY:-}\n VAPI_ASSISTANT_ID: ${VAPI_ASSISTANT_ID:-}\n VAPI_WEBHOOK_SECRET: ${VAPI_WEBHOOK_SECRET:-}\n TWILIO_ACCOUNT_SID: ${TWILIO_ACCOUNT_SID:-}\n TWILIO_AUTH_TOKEN: ${TWILIO_AUTH_TOKEN:-}\n TWILIO_PHONE_NUMBER: ${TWILIO_PHONE_NUMBER:-+19492391845}\n ports:\n - \"0.0.0.0:${BETTY_OPERATOR_HOST_PORT:-18120}:8000\"\n volumes:\n - betty_operator_data:/app/data\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 15s\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n n8n:\n image: n8nio/n8n:latest\n container_name: betty-n8n\n depends_on:\n postgres:\n condition: service_healthy\n ollama:\n condition: service_healthy\n ollama-wrapper:\n condition: service_healthy\n betty-memory:\n condition: service_healthy\n betty-actions:\n condition: service_healthy\n betty-lookup:\n condition: service_healthy\n betty-operator:\n condition: service_healthy\n environment:\n N8N_HOST: ${N8N_HOST:-localhost}\n N8N_PORT: 5678\n N8N_PROTOCOL: ${N8N_PROTOCOL:-http}\n WEBHOOK_URL: ${WEBHOOK_URL:-http://localhost:15678/}\n GENERIC_TIMEZONE: ${GENERIC_TIMEZONE:-America/Los_Angeles}\n N8N_ENCRYPTION_KEY: ${N8N_ENCRYPTION_KEY:?set N8N_ENCRYPTION_KEY in .env}\n N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS: \"true\"\n N8N_DIAGNOSTICS_ENABLED: \"false\"\n N8N_PERSONALIZATION_ENABLED: \"false\"\n N8N_SECURE_COOKIE: \"false\"\n N8N_BASIC_AUTH_ACTIVE: ${N8N_BASIC_AUTH_ACTIVE:-true}\n N8N_BASIC_AUTH_USER: ${N8N_BASIC_AUTH_USER:?set N8N_BASIC_AUTH_USER in .env}\n N8N_BASIC_AUTH_PASSWORD: ${N8N_BASIC_AUTH_PASSWORD:?set N8N_BASIC_AUTH_PASSWORD in .env}\n DB_TYPE: postgresdb\n DB_POSTGRESDB_HOST: postgres\n DB_POSTGRESDB_PORT: 5432\n DB_POSTGRESDB_DATABASE: ${POSTGRES_DB:-n8n}\n DB_POSTGRESDB_USER: ${POSTGRES_USER:-n8n}\n DB_POSTGRESDB_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n OLLAMA_WRAPPER_URL: http://ollama-wrapper:8000\n BETTY_MEMORY_URL: http://betty-memory:8000\n BETTY_ACTIONS_URL: http://betty-actions:8000\n BETTY_LOOKUP_URL: http://betty-lookup:8000\n BETTY_OPERATOR_URL: http://betty-operator:8000\n ports:\n - \"127.0.0.1:${N8N_HOST_PORT:-15678}:5678\"\n volumes:\n - n8n_data:/home/node/.n8n\n restart: unless-stopped\n security_opt:\n - no-new-privileges:true\n networks:\n - betty-net\n\nvolumes:\n postgres_data:\n chroma_data:\n ollama_data:\n betty_operator_data:\n n8n_data:\n\nnetworks:\n betty-net:\n driver: bridge\n"}, "25c2831549e96e148900a597d7f4d3873b52b2a3": {"t": 1785769585.585058, "n": 20435, "h": "28efddcffc5281ee", "c": "name: betty-stack\n\nservices:\n postgres:\n image: postgres:16-alpine\n container_name: betty-postgres\n environment:\n POSTGRES_USER: ${POSTGRES_USER:-n8n}\n POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n POSTGRES_DB: ${POSTGRES_DB:-n8n}\n volumes:\n - postgres_data:/var/lib/postgresql/data\n healthcheck:\n test: [\"CMD-SHELL\", \"pg_isready -U ${POSTGRES_USER:-n8n} -d ${POSTGRES_DB:-n8n}\"]\n interval: 10s\n timeout: 5s\n retries: 10\n start_period: 15s\n restart: unless-stopped\n security_opt:\n - no-new-privileges:true\n networks:\n - betty-net\n\n chroma:\n image: chromadb/chroma:0.5.23\n container_name: betty-chroma\n volumes:\n - chroma_data:/chroma/chroma\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/api/v1/heartbeat', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 15s\n restart: unless-stopped\n security_opt:\n - no-new-privileges:true\n networks:\n - betty-net\n\n ollama:\n image: ollama/ollama:latest\n container_name: betty-ollama\n ports:\n - \"127.0.0.1:${OLLAMA_HOST_PORT:-11435}:11434\"\n volumes:\n - ollama_data:/root/.ollama\n healthcheck:\n test: [\"CMD-SHELL\", \"ollama list >/dev/null 2>&1 || exit 1\"]\n interval: 20s\n timeout: 10s\n retries: 6\n start_period: 30s\n restart: unless-stopped\n security_opt:\n - no-new-privileges:true\n networks:\n - betty-net\n\n ollama-wrapper:\n build:\n context: ./ollama-wrapper\n image: betty/ollama-wrapper:local\n container_name: betty-ollama-wrapper\n depends_on:\n ollama:\n condition: service_healthy\n environment:\n OLLAMA_URL: http://ollama:11434\n API_USERNAME: ${WRAPPER_API_USERNAME:?set WRAPPER_API_USERNAME in .env}\n API_PASSWORD: ${WRAPPER_API_PASSWORD:?set WRAPPER_API_PASSWORD in .env}\n API_BEARER_TOKEN: ${WRAPPER_API_BEARER_TOKEN:-}\n WRAPPER_REQUEST_TIMEOUT_SECONDS: ${WRAPPER_REQUEST_TIMEOUT_SECONDS:-120}\n LOG_LEVEL: ${WRAPPER_LOG_LEVEL:-info}\n ports:\n - \"127.0.0.1:${WRAPPER_HOST_PORT:-18000}:8000\"\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 10s\n read_only: true\n tmpfs:\n - /tmp\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n betty-memory:\n build:\n context: ./services/memory\n image: betty/memory-service:local\n container_name: betty-memory\n depends_on:\n postgres:\n condition: service_healthy\n chroma:\n condition: service_healthy\n ollama:\n condition: service_healthy\n environment:\n BETTY_MEMORY_POSTGRES_HOST: postgres\n BETTY_MEMORY_POSTGRES_PORT: 5432\n BETTY_MEMORY_POSTGRES_DB: ${POSTGRES_DB:-n8n}\n BETTY_MEMORY_POSTGRES_USER: ${POSTGRES_USER:-n8n}\n BETTY_MEMORY_POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n BETTY_MEMORY_CHROMA_HOST: chroma\n BETTY_MEMORY_CHROMA_PORT: 8000\n BETTY_MEMORY_COLLECTION: ${BETTY_MEMORY_COLLECTION:-betty-v1-facts}\n BETTY_MEMORY_OLLAMA_URL: http://ollama:11434\n BETTY_MEMORY_EMBED_MODEL: ${BETTY_MEMORY_EMBED_MODEL:-nomic-embed-text}\n BETTY_MEMORY_SEMANTIC_DISTANCE_THRESHOLD: ${BETTY_MEMORY_SEMANTIC_DISTANCE_THRESHOLD:-0.45}\n BETTY_MEMORY_LOG_LEVEL: ${BETTY_MEMORY_LOG_LEVEL:-info}\n BETTY_MEMORY_DEFAULT_TIMEZONE: ${GENERIC_TIMEZONE:-America/Los_Angeles}\n BETTY_OWNER_NAME: ${BETTY_OWNER_NAME:-Vincent}\n BETTY_HOME_CITY: ${BETTY_HOME_CITY:-Corona Del Mar, CA}\n ports:\n - \"127.0.0.1:${BETTY_MEMORY_HOST_PORT:-18100}:8000\"\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 15s\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n betty-actions:\n build:\n context: ./services/actions\n image: betty/actions-service:local\n container_name: betty-actions\n depends_on:\n postgres:\n condition: service_healthy\n environment:\n BETTY_ACTIONS_POSTGRES_HOST: postgres\n BETTY_ACTIONS_POSTGRES_PORT: 5432\n BETTY_ACTIONS_POSTGRES_DB: ${POSTGRES_DB:-n8n}\n BETTY_ACTIONS_POSTGRES_USER: ${POSTGRES_USER:-n8n}\n BETTY_ACTIONS_POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n BETTY_ACTIONS_DEFAULT_TIMEZONE: ${GENERIC_TIMEZONE:-America/Los_Angeles}\n GOOGLE_CALENDAR_CLIENT_ID: ${GOOGLE_CALENDAR_CLIENT_ID:-}\n GOOGLE_CALENDAR_CLIENT_SECRET: ${GOOGLE_CALENDAR_CLIENT_SECRET:-}\n ports:\n - \"127.0.0.1:${BETTY_ACTIONS_HOST_PORT:-18110}:8000\"\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 15s\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n betty-lookup:\n build:\n context: ./services/lookup\n image: betty/lookup-service:local\n container_name: betty-lookup\n depends_on:\n postgres:\n condition: service_healthy\n ollama-wrapper:\n condition: service_healthy\n environment:\n BETTY_LOOKUP_POSTGRES_HOST: postgres\n BETTY_LOOKUP_POSTGRES_PORT: 5432\n BETTY_LOOKUP_POSTGRES_DB: ${POSTGRES_DB:-n8n}\n BETTY_LOOKUP_POSTGRES_USER: ${POSTGRES_USER:-n8n}\n BETTY_LOOKUP_POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n BETTY_LOOKUP_DEFAULT_TIMEZONE: ${GENERIC_TIMEZONE:-America/Los_Angeles}\n BETTY_OWNER_NAME: ${BETTY_OWNER_NAME:-Vincent}\n BETTY_HOME_CITY: ${BETTY_HOME_CITY:-Corona Del Mar, CA}\n BRAVE_ENABLED: ${BRAVE_ENABLED:-false}\n SEARXNG_ENABLED: ${SEARXNG_ENABLED:-true}\n SEARXNG_BASE_URL: ${SEARXNG_BASE_URL:-http://host.docker.internal:8080}\n BETTY_COST_DAILY_CAP_USD: ${BETTY_COST_DAILY_CAP_USD:-5}\n BETTY_COST_MONTHLY_CAP_USD: ${BETTY_COST_MONTHLY_CAP_USD:-50}\n BETTY_COST_WARNING_RATIO: ${BETTY_COST_WARNING_RATIO:-0.8}\n BRAVE_SEARCH_API_KEY: ${BRAVE_SEARCH_API_KEY:-}\n BETTY_LOOKUP_WRAPPER_URL: http://ollama-wrapper:8000/v1/chat/completions\n BETTY_LOOKUP_WRAPPER_USERNAME: ${WRAPPER_API_USERNAME:?set WRAPPER_API_USERNAME in .env}\n BETTY_LOOKUP_WRAPPER_PASSWORD: ${WRAPPER_API_PASSWORD:?set WRAPPER_API_PASSWORD in .env}\n BETTY_LOOKUP_WRAPPER_BEARER_TOKEN: ${WRAPPER_API_BEARER_TOKEN:-}\n BETTY_LOOKUP_MODEL: ${BETTY_LOOKUP_MODEL:-}\n BETTY_LOOKUP_WRAPPER_TIMEOUT_SECONDS: ${WRAPPER_REQUEST_TIMEOUT_SECONDS:-120}\n ports:\n - \"127.0.0.1:${BETTY_LOOKUP_HOST_PORT:-18130}:8000\"\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 15s\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n betty-brain:\n build:\n context: ./services/brain\n image: betty/brain-service:local\n container_name: betty-brain\n depends_on:\n postgres:\n condition: service_healthy\n betty-memory:\n condition: service_healthy\n betty-lookup:\n condition: service_healthy\n environment:\n BETTY_BRAIN_POSTGRES_HOST: postgres\n BETTY_BRAIN_POSTGRES_PORT: 5432\n BETTY_BRAIN_POSTGRES_DB: ${POSTGRES_DB:-n8n}\n BETTY_BRAIN_POSTGRES_USER: ${POSTGRES_USER:-n8n}\n BETTY_BRAIN_POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n BETTY_BRAIN_MODEL: ${BETTY_BRAIN_MODEL:-claude-haiku-4-5}\n BETTY_BRAIN_MAX_TOKENS: ${BETTY_BRAIN_MAX_TOKENS:-2000}\n BETTY_BRAIN_MAX_AGENT_ITERATIONS: ${BETTY_BRAIN_MAX_AGENT_ITERATIONS:-8}\n BETTY_BRAIN_MEMORY_URL: http://betty-memory:8000\n BETTY_BRAIN_LOOKUP_URL: http://betty-lookup:8000\n BETTY_BRAIN_OPERATOR_URL: http://betty-operator:8000\n BETTY_BRAIN_TOOL_HTTP_TIMEOUT: ${BETTY_BRAIN_TOOL_HTTP_TIMEOUT:-30}\n BETTY_BRAIN_LOOKUP_HTTP_TIMEOUT: ${BETTY_BRAIN_LOOKUP_HTTP_TIMEOUT:-60}\n BETTY_OWNER_EMAIL: ${BETTY_OWNER_EMAIL:-vincentthinks@gmail.com}\n BETTY_SLACK_BOT_TOKEN: ${BETTY_SLACK_BOT_TOKEN:-}\n BETTY_SLACK_DEFAULT_DELEGATION_CHANNEL: ${BETTY_SLACK_DEFAULT_DELEGATION_CHANNEL:-#betty-station}\n ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:?set ANTHROPIC_API_KEY in .env}\n BETTY_COST_DAILY_CAP_USD: ${BETTY_COST_DAILY_CAP_USD:-1}\n BETTY_COST_MONTHLY_CAP_USD: ${BETTY_COST_MONTHLY_CAP_USD:-30}\n BETTY_COST_WARNING_RATIO: ${BETTY_COST_WARNING_RATIO:-0.8}\n BETTY_OWNER_NAME: ${BETTY_OWNER_NAME:-Vincent}\n BETTY_HOME_CITY: ${BETTY_HOME_CITY:-Rancho Santa Margarita, CA}\n ports:\n - \"127.0.0.1:${BETTY_BRAIN_HOST_PORT:-18140}:8000\"\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 15s\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n betty-daemon:\n build:\n context: ./services/betty-slack-daemon\n image: betty/betty-slack-daemon:local\n container_name: betty-daemon\n depends_on:\n betty-brain:\n condition: service_healthy\n betty-memory:\n condition: service_healthy\n environment:\n BETTY_SLACK_APP_TOKEN: ${BETTY_SLACK_APP_TOKEN:?set BETTY_SLACK_APP_TOKEN in .env after creating Betty's Slack app}\n BETTY_SLACK_BOT_TOKEN: ${BETTY_SLACK_BOT_TOKEN:?set BETTY_SLACK_BOT_TOKEN in .env after creating Betty's Slack app}\n BETTY_BRAIN_URL: http://betty-brain:8000\n BETTY_BRAIN_TIMEOUT_SECONDS: ${BETTY_BRAIN_TIMEOUT_SECONDS:-60}\n BETTY_DAEMON_MEMORY_URL: http://betty-memory:8000\n BETTY_DAEMON_MEMORY_TIMEOUT_SECONDS: ${BETTY_DAEMON_MEMORY_TIMEOUT_SECONDS:-15}\n BETTY_OWNER_SLACK_USER_ID: ${BETTY_OWNER_SLACK_USER_ID:-}\n BETTY_DAEMON_REMINDER_POLL_INTERVAL_SECONDS: ${BETTY_DAEMON_REMINDER_POLL_INTERVAL_SECONDS:-60}\n BETTY_DAEMON_REMINDER_LOOKAHEAD_SECONDS: ${BETTY_DAEMON_REMINDER_LOOKAHEAD_SECONDS:-60}\n BETTY_DAEMON_DATA_DIR: /data\n BETTY_DAEMON_THREAD_KEEP_MSGS: ${BETTY_DAEMON_THREAD_KEEP_MSGS:-20}\n volumes:\n - ./data/betty-daemon:/data\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n betty-operator:\n build:\n context: ./services/operator\n image: betty/operator-service:local\n container_name: betty-operator\n depends_on:\n postgres:\n condition: service_healthy\n betty-memory:\n condition: service_healthy\n betty-actions:\n condition: service_healthy\n betty-lookup:\n condition: service_healthy\n environment:\n BETTY_OPERATOR_POSTGRES_HOST: postgres\n BETTY_OPERATOR_POSTGRES_PORT: 5432\n BETTY_OPERATOR_POSTGRES_DB: ${POSTGRES_DB:-n8n}\n BETTY_OPERATOR_POSTGRES_USER: ${POSTGRES_USER:-n8n}\n BETTY_OPERATOR_POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n BETTY_OPERATOR_MEMORY_URL: http://betty-memory:8000\n BETTY_OPERATOR_ACTIONS_URL: http://betty-actions:8000\n BETTY_OPERATOR_LOOKUP_URL: http://betty-lookup:8000\n BETTY_OPERATOR_BRAIN_URL: http://betty-brain:8000\n BETTY_OPERATOR_BRAIN_TIMEOUT_SECONDS: ${BETTY_OPERATOR_BRAIN_TIMEOUT_SECONDS:-60}\n BETTY_OPERATOR_BRAIN_DEFAULT_ENABLED: ${BETTY_OPERATOR_BRAIN_DEFAULT_ENABLED:-true}\n BETTY_OPERATOR_WRAPPER_URL: http://ollama-wrapper:8000/v1/chat/completions\n BETTY_OPERATOR_WRAPPER_USERNAME: ${WRAPPER_API_USERNAME:?set WRAPPER_API_USERNAME in .env}\n BETTY_OPERATOR_WRAPPER_PASSWORD: ${WRAPPER_API_PASSWORD:?set WRAPPER_API_PASSWORD in .env}\n BETTY_OPERATOR_WRAPPER_BEARER_TOKEN: ${WRAPPER_API_BEARER_TOKEN:-}\n BETTY_OPERATOR_WRAPPER_TIMEOUT_SECONDS: ${WRAPPER_REQUEST_TIMEOUT_SECONDS:-120}\n BETTY_OPERATOR_DEFAULT_TIMEZONE: ${GENERIC_TIMEZONE:-America/Los_Angeles}\n BETTY_PUBLIC_BASE_URL: ${BETTY_PUBLIC_BASE_URL:-https://YOUR_PUBLIC_URL}\n BETTY_OWNER_PHONE: ${BETTY_OWNER_PHONE:-}\n BETTY_OWNER_NAME: ${BETTY_OWNER_NAME:-Vincent}\n BETTY_HOME_CITY: ${BETTY_HOME_CITY:-Corona Del Mar, CA}\n BETTY_OPERATOR_STT_PROVIDER: ${BETTY_OPERATOR_STT_PROVIDER:-whisper_local}\n BETTY_OPERATOR_WHISPER_MODEL: ${BETTY_OPERATOR_WHISPER_MODEL:-base.en}\n BETTY_OPERATOR_WHISPER_DEVICE: ${BETTY_OPERATOR_WHISPER_DEVICE:-cpu}\n BETTY_OPERATOR_WHISPER_COMPUTE_TYPE: ${BETTY_OPERATOR_WHISPER_COMPUTE_TYPE:-int8}\n BETTY_OPERATOR_WHISPER_DATA_DIR: ${BETTY_OPERATOR_WHISPER_DATA_DIR:-/app/data/whisper}\n BETTY_MODEL_MODE: ${BETTY_MODEL_MODE:-default}\n BETTY_DEFAULT_CHAT_PROVIDER: ${BETTY_DEFAULT_CHAT_PROVIDER:-auto}\n BETTY_DEFAULT_CHAT_MODEL: ${BETTY_DEFAULT_CHAT_MODEL:-deepseek-chat}\n BETTY_DEFAULT_LOCAL_CHAT_MODEL: ${BETTY_DEFAULT_LOCAL_CHAT_MODEL:-llama3.1:8b}\n BETTY_MODEL_DOLPHIN: ${BETTY_MODEL_DOLPHIN:-dolphin-llama3:70b}\n BETTY_MODEL_MEDICAL: ${BETTY_MODEL_MEDICAL:-medllama2}\n BETTY_MODEL_MUSIC: ${BETTY_MODEL_MUSIC:-llamusic/llamusic}\n BETTY_MODEL_IMAGE: ${BETTY_MODEL_IMAGE:-llava:13b}\n # Slice 8: photo/scan vision. DGX direct (bypasses ollama-wrapper since wrapper\n # talks to local betty-ollama which has no LLaVA). Set to ollama-wrapper URL\n # if DGX is unreachable and you've pulled llava:13b locally.\n BETTY_VISION_OLLAMA_URL: ${BETTY_VISION_OLLAMA_URL:-http://192.168.68.79:11434}\n BETTY_VISION_TIMEOUT_SECONDS: ${BETTY_VISION_TIMEOUT_SECONDS:-90}\n # Cloud vision tier (Photo+ button → Haiku 4.5 vision via Anthropic API).\n ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-}\n BETTY_VISION_CLOUD_MODEL: ${BETTY_VISION_CLOUD_MODEL:-claude-haiku-4-5}\n BETTY_VISION_CLOUD_INPUT_COST_PER_1M_USD: ${BETTY_VISION_CLOUD_INPUT_COST_PER_1M_USD:-1.0}\n BETTY_VISION_CLOUD_OUTPUT_COST_PER_1M_USD: ${BETTY_VISION_CLOUD_OUTPUT_COST_PER_1M_USD:-5.0}\n BETTY_MODEL_WRITE: ${BETTY_MODEL_WRITE:-HammerAI/mythomax-l2}\n BETTY_MODEL_CODE: ${BETTY_MODEL_CODE:-qwen2.5-coder:32b}\n BETTY_MODEL_RESEARCH: ${BETTY_MODEL_RESEARCH:-nemotron}\n BETTY_MODEL_FAST: ${BETTY_MODEL_FAST:-gemma2:27b}\n DEEPSEEK_API_KEY: ${DEEPSEEK_API_KEY:-}\n DEEPSEEK_BASE_URL: ${DEEPSEEK_BASE_URL:-https://api.deepseek.com}\n BETTY_DEEPSEEK_MODEL: ${BETTY_DEEPSEEK_MODEL:-deepseek-chat}\n BETTY_DEEPSEEK_REASONER_MODEL: ${BETTY_DEEPSEEK_REASONER_MODEL:-deepseek-reasoner}\n BETTY_DEEPSEEK_TIMEOUT_SECONDS: ${BETTY_DEEPSEEK_TIMEOUT_SECONDS:-120}\n BETTY_DEEPSEEK_CHAT_INPUT_COST_PER_1M_USD: ${BETTY_DEEPSEEK_CHAT_INPUT_COST_PER_1M_USD:-0.27}\n BETTY_DEEPSEEK_CHAT_OUTPUT_COST_PER_1M_USD: ${BETTY_DEEPSEEK_CHAT_OUTPUT_COST_PER_1M_USD:-1.10}\n BETTY_DEEPSEEK_REASONER_INPUT_COST_PER_1M_USD: ${BETTY_DEEPSEEK_REASONER_INPUT_COST_PER_1M_USD:-0.55}\n BETTY_DEEPSEEK_REASONER_OUTPUT_COST_PER_1M_USD: ${BETTY_DEEPSEEK_REASONER_OUTPUT_COST_PER_1M_USD:-2.19}\n KOKORO_ENABLED: ${KOKORO_ENABLED:-true}\n KOKORO_DEFAULT_VOICE: ${KOKORO_DEFAULT_VOICE:-af_bella}\n BETTY_OPERATOR_KOKORO_SPEED: ${BETTY_OPERATOR_KOKORO_SPEED:-1.0}\n BETTY_OPERATOR_KOKORO_DATA_DIR: ${BETTY_OPERATOR_KOKORO_DATA_DIR:-/app/data/kokoro}\n ELEVENLABS_ENABLED: ${ELEVENLABS_ENABLED:-true}\n TWILIO_ENABLED: ${TWILIO_ENABLED:-true}\n VAPI_ENABLED: ${VAPI_ENABLED:-true}\n BETTY_COST_DAILY_CAP_USD: ${BETTY_COST_DAILY_CAP_USD:-5}\n BETTY_COST_MONTHLY_CAP_USD: ${BETTY_COST_MONTHLY_CAP_USD:-50}\n BETTY_COST_WARNING_RATIO: ${BETTY_COST_WARNING_RATIO:-0.8}\n BETTY_ELEVENLABS_COST_PER_1K_CHARS_USD: ${BETTY_ELEVENLABS_COST_PER_1K_CHARS_USD:-0.30}\n BETTY_TWILIO_SMS_COST_USD: ${BETTY_TWILIO_SMS_COST_USD:-0.008}\n BETTY_VAPI_COST_PER_MINUTE_USD: ${BETTY_VAPI_COST_PER_MINUTE_USD:-0.075}\n ELEVENLABS_API_KEY: ${ELEVENLABS_API_KEY:-}\n ELEVENLABS_VOICE_ID: ${ELEVENLABS_VOICE_ID:-4tRn1lSkEn13EVTuqb0g}\n ELEVENLABS_MODEL_ID: ${ELEVENLABS_MODEL_ID:-eleven_turbo_v2_5}\n PIPER_VOICE_MODEL: ${PIPER_VOICE_MODEL:-en_US-lessac-medium}\n BETTY_OPERATOR_PIPER_DATA_DIR: ${BETTY_OPERATOR_PIPER_DATA_DIR:-/app/data/piper}\n GOOGLE_CALENDAR_CLIENT_ID: ${GOOGLE_CALENDAR_CLIENT_ID:-}\n GOOGLE_CALENDAR_CLIENT_SECRET: ${GOOGLE_CALENDAR_CLIENT_SECRET:-}\n GOOGLE_CALENDAR_OAUTH_REDIRECT_URI: ${GOOGLE_CALENDAR_OAUTH_REDIRECT_URI:-}\n BETTY_OPERATOR_RATE_LIMIT_WINDOW_SECONDS: ${BETTY_OPERATOR_RATE_LIMIT_WINDOW_SECONDS:-60}\n BETTY_OPERATOR_ELEVENLABS_RATE_LIMIT: ${BETTY_OPERATOR_ELEVENLABS_RATE_LIMIT:-20}\n BETTY_OPERATOR_TWILIO_RATE_LIMIT: ${BETTY_OPERATOR_TWILIO_RATE_LIMIT:-20}\n VAPI_API_KEY: ${VAPI_API_KEY:-}\n VAPI_ASSISTANT_ID: ${VAPI_ASSISTANT_ID:-}\n VAPI_WEBHOOK_SECRET: ${VAPI_WEBHOOK_SECRET:-}\n TWILIO_ACCOUNT_SID: ${TWILIO_ACCOUNT_SID:-}\n TWILIO_AUTH_TOKEN: ${TWILIO_AUTH_TOKEN:-}\n TWILIO_PHONE_NUMBER: ${TWILIO_PHONE_NUMBER:-+19492391845}\n ports:\n - \"0.0.0.0:${BETTY_OPERATOR_HOST_PORT:-18120}:8000\"\n volumes:\n - betty_operator_data:/app/data\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 15s\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n n8n:\n image: n8nio/n8n:latest\n container_name: betty-n8n\n depends_on:\n postgres:\n condition: service_healthy\n ollama:\n condition: service_healthy\n ollama-wrapper:\n condition: service_healthy\n betty-memory:\n condition: service_healthy\n betty-actions:\n condition: service_healthy\n betty-lookup:\n condition: service_healthy\n betty-operator:\n condition: service_healthy\n environment:\n N8N_HOST: ${N8N_HOST:-localhost}\n N8N_PORT: 5678\n N8N_PROTOCOL: ${N8N_PROTOCOL:-http}\n WEBHOOK_URL: ${WEBHOOK_URL:-http://localhost:15678/}\n GENERIC_TIMEZONE: ${GENERIC_TIMEZONE:-America/Los_Angeles}\n N8N_ENCRYPTION_KEY: ${N8N_ENCRYPTION_KEY:?set N8N_ENCRYPTION_KEY in .env}\n N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS: \"true\"\n N8N_DIAGNOSTICS_ENABLED: \"false\"\n N8N_PERSONALIZATION_ENABLED: \"false\"\n N8N_SECURE_COOKIE: \"false\"\n N8N_BASIC_AUTH_ACTIVE: ${N8N_BASIC_AUTH_ACTIVE:-true}\n N8N_BASIC_AUTH_USER: ${N8N_BASIC_AUTH_USER:?set N8N_BASIC_AUTH_USER in .env}\n N8N_BASIC_AUTH_PASSWORD: ${N8N_BASIC_AUTH_PASSWORD:?set N8N_BASIC_AUTH_PASSWORD in .env}\n DB_TYPE: postgresdb\n DB_POSTGRESDB_HOST: postgres\n DB_POSTGRESDB_PORT: 5432\n DB_POSTGRESDB_DATABASE: ${POSTGRES_DB:-n8n}\n DB_POSTGRESDB_USER: ${POSTGRES_USER:-n8n}\n DB_POSTGRESDB_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n OLLAMA_WRAPPER_URL: http://ollama-wrapper:8000\n BETTY_MEMORY_URL: http://betty-memory:8000\n BETTY_ACTIONS_URL: http://betty-actions:8000\n BETTY_LOOKUP_URL: http://betty-lookup:8000\n BETTY_OPERATOR_URL: http://betty-operator:8000\n ports:\n - \"127.0.0.1:${N8N_HOST_PORT:-15678}:5678\"\n volumes:\n - n8n_data:/home/node/.n8n\n restart: unless-stopped\n security_opt:\n - no-new-privileges:true\n networks:\n - betty-net\n\nvolumes:\n postgres_data:\n chroma_data:\n ollama_data:\n betty_operator_data:\n n8n_data:\n\nnetworks:\n betty-net:\n driver: bridge\n"}, "486301d090aa5073d733b53d96608a638d99812b": {"t": 1785769589.1776931, "n": 20071, "h": "e5908ac6a6bf4618", "c": "name: betty-stack\n\nservices:\n postgres:\n image: postgres:16-alpine\n container_name: betty-postgres\n environment:\n POSTGRES_USER: ${POSTGRES_USER:-n8n}\n POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n POSTGRES_DB: ${POSTGRES_DB:-n8n}\n volumes:\n - postgres_data:/var/lib/postgresql/data\n healthcheck:\n test: [\"CMD-SHELL\", \"pg_isready -U ${POSTGRES_USER:-n8n} -d ${POSTGRES_DB:-n8n}\"]\n interval: 10s\n timeout: 5s\n retries: 10\n start_period: 15s\n restart: unless-stopped\n security_opt:\n - no-new-privileges:true\n networks:\n - betty-net\n\n chroma:\n image: chromadb/chroma:0.5.23\n container_name: betty-chroma\n volumes:\n - chroma_data:/chroma/chroma\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/api/v1/heartbeat', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 15s\n restart: unless-stopped\n security_opt:\n - no-new-privileges:true\n networks:\n - betty-net\n\n ollama:\n image: ollama/ollama:latest\n container_name: betty-ollama\n ports:\n - \"127.0.0.1:${OLLAMA_HOST_PORT:-11435}:11434\"\n volumes:\n - ollama_data:/root/.ollama\n healthcheck:\n test: [\"CMD-SHELL\", \"ollama list >/dev/null 2>&1 || exit 1\"]\n interval: 20s\n timeout: 10s\n retries: 6\n start_period: 30s\n restart: unless-stopped\n security_opt:\n - no-new-privileges:true\n networks:\n - betty-net\n\n ollama-wrapper:\n build:\n context: ./ollama-wrapper\n image: betty/ollama-wrapper:local\n container_name: betty-ollama-wrapper\n depends_on:\n ollama:\n condition: service_healthy\n environment:\n OLLAMA_URL: http://ollama:11434\n API_USERNAME: ${WRAPPER_API_USERNAME:?set WRAPPER_API_USERNAME in .env}\n API_PASSWORD: ${WRAPPER_API_PASSWORD:?set WRAPPER_API_PASSWORD in .env}\n API_BEARER_TOKEN: ${WRAPPER_API_BEARER_TOKEN:-}\n WRAPPER_REQUEST_TIMEOUT_SECONDS: ${WRAPPER_REQUEST_TIMEOUT_SECONDS:-120}\n LOG_LEVEL: ${WRAPPER_LOG_LEVEL:-info}\n ports:\n - \"127.0.0.1:${WRAPPER_HOST_PORT:-18000}:8000\"\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 10s\n read_only: true\n tmpfs:\n - /tmp\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n betty-memory:\n build:\n context: ./services/memory\n image: betty/memory-service:local\n container_name: betty-memory\n depends_on:\n postgres:\n condition: service_healthy\n chroma:\n condition: service_healthy\n ollama:\n condition: service_healthy\n environment:\n BETTY_MEMORY_POSTGRES_HOST: postgres\n BETTY_MEMORY_POSTGRES_PORT: 5432\n BETTY_MEMORY_POSTGRES_DB: ${POSTGRES_DB:-n8n}\n BETTY_MEMORY_POSTGRES_USER: ${POSTGRES_USER:-n8n}\n BETTY_MEMORY_POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n BETTY_MEMORY_CHROMA_HOST: chroma\n BETTY_MEMORY_CHROMA_PORT: 8000\n BETTY_MEMORY_COLLECTION: ${BETTY_MEMORY_COLLECTION:-betty-v1-facts}\n BETTY_MEMORY_OLLAMA_URL: http://ollama:11434\n BETTY_MEMORY_EMBED_MODEL: ${BETTY_MEMORY_EMBED_MODEL:-nomic-embed-text}\n BETTY_MEMORY_SEMANTIC_DISTANCE_THRESHOLD: ${BETTY_MEMORY_SEMANTIC_DISTANCE_THRESHOLD:-0.45}\n BETTY_MEMORY_LOG_LEVEL: ${BETTY_MEMORY_LOG_LEVEL:-info}\n BETTY_MEMORY_DEFAULT_TIMEZONE: ${GENERIC_TIMEZONE:-America/Los_Angeles}\n BETTY_OWNER_NAME: ${BETTY_OWNER_NAME:-Vincent}\n BETTY_HOME_CITY: ${BETTY_HOME_CITY:-Corona Del Mar, CA}\n ports:\n - \"127.0.0.1:${BETTY_MEMORY_HOST_PORT:-18100}:8000\"\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 15s\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n betty-actions:\n build:\n context: ./services/actions\n image: betty/actions-service:local\n container_name: betty-actions\n depends_on:\n postgres:\n condition: service_healthy\n environment:\n BETTY_ACTIONS_POSTGRES_HOST: postgres\n BETTY_ACTIONS_POSTGRES_PORT: 5432\n BETTY_ACTIONS_POSTGRES_DB: ${POSTGRES_DB:-n8n}\n BETTY_ACTIONS_POSTGRES_USER: ${POSTGRES_USER:-n8n}\n BETTY_ACTIONS_POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n BETTY_ACTIONS_DEFAULT_TIMEZONE: ${GENERIC_TIMEZONE:-America/Los_Angeles}\n GOOGLE_CALENDAR_CLIENT_ID: ${GOOGLE_CALENDAR_CLIENT_ID:-}\n GOOGLE_CALENDAR_CLIENT_SECRET: ${GOOGLE_CALENDAR_CLIENT_SECRET:-}\n ports:\n - \"127.0.0.1:${BETTY_ACTIONS_HOST_PORT:-18110}:8000\"\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 15s\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n betty-lookup:\n build:\n context: ./services/lookup\n image: betty/lookup-service:local\n container_name: betty-lookup\n depends_on:\n postgres:\n condition: service_healthy\n ollama-wrapper:\n condition: service_healthy\n environment:\n BETTY_LOOKUP_POSTGRES_HOST: postgres\n BETTY_LOOKUP_POSTGRES_PORT: 5432\n BETTY_LOOKUP_POSTGRES_DB: ${POSTGRES_DB:-n8n}\n BETTY_LOOKUP_POSTGRES_USER: ${POSTGRES_USER:-n8n}\n BETTY_LOOKUP_POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n BETTY_LOOKUP_DEFAULT_TIMEZONE: ${GENERIC_TIMEZONE:-America/Los_Angeles}\n BETTY_OWNER_NAME: ${BETTY_OWNER_NAME:-Vincent}\n BETTY_HOME_CITY: ${BETTY_HOME_CITY:-Corona Del Mar, CA}\n BRAVE_ENABLED: ${BRAVE_ENABLED:-false}\n SEARXNG_ENABLED: ${SEARXNG_ENABLED:-true}\n SEARXNG_BASE_URL: ${SEARXNG_BASE_URL:-http://host.docker.internal:8080}\n BETTY_COST_DAILY_CAP_USD: ${BETTY_COST_DAILY_CAP_USD:-5}\n BETTY_COST_MONTHLY_CAP_USD: ${BETTY_COST_MONTHLY_CAP_USD:-50}\n BETTY_COST_WARNING_RATIO: ${BETTY_COST_WARNING_RATIO:-0.8}\n BRAVE_SEARCH_API_KEY: ${BRAVE_SEARCH_API_KEY:-}\n BETTY_LOOKUP_WRAPPER_URL: http://ollama-wrapper:8000/v1/chat/completions\n BETTY_LOOKUP_WRAPPER_USERNAME: ${WRAPPER_API_USERNAME:?set WRAPPER_API_USERNAME in .env}\n BETTY_LOOKUP_WRAPPER_PASSWORD: ${WRAPPER_API_PASSWORD:?set WRAPPER_API_PASSWORD in .env}\n BETTY_LOOKUP_WRAPPER_BEARER_TOKEN: ${WRAPPER_API_BEARER_TOKEN:-}\n BETTY_LOOKUP_MODEL: ${BETTY_LOOKUP_MODEL:-}\n BETTY_LOOKUP_WRAPPER_TIMEOUT_SECONDS: ${WRAPPER_REQUEST_TIMEOUT_SECONDS:-120}\n ports:\n - \"127.0.0.1:${BETTY_LOOKUP_HOST_PORT:-18130}:8000\"\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 15s\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n betty-brain:\n build:\n context: ./services/brain\n image: betty/brain-service:local\n container_name: betty-brain\n depends_on:\n postgres:\n condition: service_healthy\n betty-memory:\n condition: service_healthy\n betty-lookup:\n condition: service_healthy\n environment:\n BETTY_BRAIN_POSTGRES_HOST: postgres\n BETTY_BRAIN_POSTGRES_PORT: 5432\n BETTY_BRAIN_POSTGRES_DB: ${POSTGRES_DB:-n8n}\n BETTY_BRAIN_POSTGRES_USER: ${POSTGRES_USER:-n8n}\n BETTY_BRAIN_POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n BETTY_BRAIN_MODEL: ${BETTY_BRAIN_MODEL:-claude-haiku-4-5}\n BETTY_BRAIN_MAX_TOKENS: ${BETTY_BRAIN_MAX_TOKENS:-2000}\n BETTY_BRAIN_MAX_AGENT_ITERATIONS: ${BETTY_BRAIN_MAX_AGENT_ITERATIONS:-8}\n BETTY_BRAIN_MEMORY_URL: http://betty-memory:8000\n BETTY_BRAIN_LOOKUP_URL: http://betty-lookup:8000\n BETTY_BRAIN_TOOL_HTTP_TIMEOUT: ${BETTY_BRAIN_TOOL_HTTP_TIMEOUT:-30}\n BETTY_BRAIN_LOOKUP_HTTP_TIMEOUT: ${BETTY_BRAIN_LOOKUP_HTTP_TIMEOUT:-60}\n BETTY_SLACK_BOT_TOKEN: ${BETTY_SLACK_BOT_TOKEN:-}\n BETTY_SLACK_DEFAULT_DELEGATION_CHANNEL: ${BETTY_SLACK_DEFAULT_DELEGATION_CHANNEL:-#betty-station}\n ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:?set ANTHROPIC_API_KEY in .env}\n BETTY_COST_DAILY_CAP_USD: ${BETTY_COST_DAILY_CAP_USD:-1}\n BETTY_COST_MONTHLY_CAP_USD: ${BETTY_COST_MONTHLY_CAP_USD:-30}\n BETTY_COST_WARNING_RATIO: ${BETTY_COST_WARNING_RATIO:-0.8}\n BETTY_OWNER_NAME: ${BETTY_OWNER_NAME:-Vincent}\n BETTY_HOME_CITY: ${BETTY_HOME_CITY:-Rancho Santa Margarita, CA}\n ports:\n - \"127.0.0.1:${BETTY_BRAIN_HOST_PORT:-18140}:8000\"\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 15s\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n betty-daemon:\n build:\n context: ./services/betty-slack-daemon\n image: betty/betty-slack-daemon:local\n container_name: betty-daemon\n depends_on:\n betty-brain:\n condition: service_healthy\n betty-memory:\n condition: service_healthy\n environment:\n BETTY_SLACK_APP_TOKEN: ${BETTY_SLACK_APP_TOKEN:?set BETTY_SLACK_APP_TOKEN in .env after creating Betty's Slack app}\n BETTY_SLACK_BOT_TOKEN: ${BETTY_SLACK_BOT_TOKEN:?set BETTY_SLACK_BOT_TOKEN in .env after creating Betty's Slack app}\n BETTY_BRAIN_URL: http://betty-brain:8000\n BETTY_BRAIN_TIMEOUT_SECONDS: ${BETTY_BRAIN_TIMEOUT_SECONDS:-60}\n BETTY_DAEMON_MEMORY_URL: http://betty-memory:8000\n BETTY_DAEMON_MEMORY_TIMEOUT_SECONDS: ${BETTY_DAEMON_MEMORY_TIMEOUT_SECONDS:-15}\n BETTY_OWNER_SLACK_USER_ID: ${BETTY_OWNER_SLACK_USER_ID:-}\n BETTY_DAEMON_REMINDER_POLL_INTERVAL_SECONDS: ${BETTY_DAEMON_REMINDER_POLL_INTERVAL_SECONDS:-60}\n BETTY_DAEMON_REMINDER_LOOKAHEAD_SECONDS: ${BETTY_DAEMON_REMINDER_LOOKAHEAD_SECONDS:-60}\n BETTY_DAEMON_DATA_DIR: /data\n BETTY_DAEMON_THREAD_KEEP_MSGS: ${BETTY_DAEMON_THREAD_KEEP_MSGS:-20}\n volumes:\n - ./data/betty-daemon:/data\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n betty-operator:\n build:\n context: ./services/operator\n image: betty/operator-service:local\n container_name: betty-operator\n depends_on:\n postgres:\n condition: service_healthy\n betty-memory:\n condition: service_healthy\n betty-actions:\n condition: service_healthy\n betty-lookup:\n condition: service_healthy\n environment:\n BETTY_OPERATOR_POSTGRES_HOST: postgres\n BETTY_OPERATOR_POSTGRES_PORT: 5432\n BETTY_OPERATOR_POSTGRES_DB: ${POSTGRES_DB:-n8n}\n BETTY_OPERATOR_POSTGRES_USER: ${POSTGRES_USER:-n8n}\n BETTY_OPERATOR_POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n BETTY_OPERATOR_MEMORY_URL: http://betty-memory:8000\n BETTY_OPERATOR_ACTIONS_URL: http://betty-actions:8000\n BETTY_OPERATOR_LOOKUP_URL: http://betty-lookup:8000\n BETTY_OPERATOR_WRAPPER_URL: http://ollama-wrapper:8000/v1/chat/completions\n BETTY_OPERATOR_WRAPPER_USERNAME: ${WRAPPER_API_USERNAME:?set WRAPPER_API_USERNAME in .env}\n BETTY_OPERATOR_WRAPPER_PASSWORD: ${WRAPPER_API_PASSWORD:?set WRAPPER_API_PASSWORD in .env}\n BETTY_OPERATOR_WRAPPER_BEARER_TOKEN: ${WRAPPER_API_BEARER_TOKEN:-}\n BETTY_OPERATOR_WRAPPER_TIMEOUT_SECONDS: ${WRAPPER_REQUEST_TIMEOUT_SECONDS:-120}\n BETTY_OPERATOR_DEFAULT_TIMEZONE: ${GENERIC_TIMEZONE:-America/Los_Angeles}\n BETTY_PUBLIC_BASE_URL: ${BETTY_PUBLIC_BASE_URL:-https://YOUR_PUBLIC_URL}\n BETTY_OWNER_PHONE: ${BETTY_OWNER_PHONE:-}\n BETTY_OWNER_NAME: ${BETTY_OWNER_NAME:-Vincent}\n BETTY_HOME_CITY: ${BETTY_HOME_CITY:-Corona Del Mar, CA}\n BETTY_OPERATOR_STT_PROVIDER: ${BETTY_OPERATOR_STT_PROVIDER:-whisper_local}\n BETTY_OPERATOR_WHISPER_MODEL: ${BETTY_OPERATOR_WHISPER_MODEL:-base.en}\n BETTY_OPERATOR_WHISPER_DEVICE: ${BETTY_OPERATOR_WHISPER_DEVICE:-cpu}\n BETTY_OPERATOR_WHISPER_COMPUTE_TYPE: ${BETTY_OPERATOR_WHISPER_COMPUTE_TYPE:-int8}\n BETTY_OPERATOR_WHISPER_DATA_DIR: ${BETTY_OPERATOR_WHISPER_DATA_DIR:-/app/data/whisper}\n BETTY_MODEL_MODE: ${BETTY_MODEL_MODE:-default}\n BETTY_DEFAULT_CHAT_PROVIDER: ${BETTY_DEFAULT_CHAT_PROVIDER:-auto}\n BETTY_DEFAULT_CHAT_MODEL: ${BETTY_DEFAULT_CHAT_MODEL:-deepseek-chat}\n BETTY_DEFAULT_LOCAL_CHAT_MODEL: ${BETTY_DEFAULT_LOCAL_CHAT_MODEL:-llama3.1:8b}\n BETTY_MODEL_DOLPHIN: ${BETTY_MODEL_DOLPHIN:-dolphin-llama3:70b}\n BETTY_MODEL_MEDICAL: ${BETTY_MODEL_MEDICAL:-medllama2}\n BETTY_MODEL_MUSIC: ${BETTY_MODEL_MUSIC:-llamusic/llamusic}\n BETTY_MODEL_IMAGE: ${BETTY_MODEL_IMAGE:-llava:13b}\n # Slice 8: photo/scan vision. DGX direct (bypasses ollama-wrapper since wrapper\n # talks to local betty-ollama which has no LLaVA). Set to ollama-wrapper URL\n # if DGX is unreachable and you've pulled llava:13b locally.\n BETTY_VISION_OLLAMA_URL: ${BETTY_VISION_OLLAMA_URL:-http://192.168.68.79:11434}\n BETTY_VISION_TIMEOUT_SECONDS: ${BETTY_VISION_TIMEOUT_SECONDS:-90}\n # Cloud vision tier (Photo+ button → Haiku 4.5 vision via Anthropic API).\n ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-}\n BETTY_VISION_CLOUD_MODEL: ${BETTY_VISION_CLOUD_MODEL:-claude-haiku-4-5}\n BETTY_VISION_CLOUD_INPUT_COST_PER_1M_USD: ${BETTY_VISION_CLOUD_INPUT_COST_PER_1M_USD:-1.0}\n BETTY_VISION_CLOUD_OUTPUT_COST_PER_1M_USD: ${BETTY_VISION_CLOUD_OUTPUT_COST_PER_1M_USD:-5.0}\n BETTY_MODEL_WRITE: ${BETTY_MODEL_WRITE:-HammerAI/mythomax-l2}\n BETTY_MODEL_CODE: ${BETTY_MODEL_CODE:-qwen2.5-coder:32b}\n BETTY_MODEL_RESEARCH: ${BETTY_MODEL_RESEARCH:-nemotron}\n BETTY_MODEL_FAST: ${BETTY_MODEL_FAST:-gemma2:27b}\n DEEPSEEK_API_KEY: ${DEEPSEEK_API_KEY:-}\n DEEPSEEK_BASE_URL: ${DEEPSEEK_BASE_URL:-https://api.deepseek.com}\n BETTY_DEEPSEEK_MODEL: ${BETTY_DEEPSEEK_MODEL:-deepseek-chat}\n BETTY_DEEPSEEK_REASONER_MODEL: ${BETTY_DEEPSEEK_REASONER_MODEL:-deepseek-reasoner}\n BETTY_DEEPSEEK_TIMEOUT_SECONDS: ${BETTY_DEEPSEEK_TIMEOUT_SECONDS:-120}\n BETTY_DEEPSEEK_CHAT_INPUT_COST_PER_1M_USD: ${BETTY_DEEPSEEK_CHAT_INPUT_COST_PER_1M_USD:-0.27}\n BETTY_DEEPSEEK_CHAT_OUTPUT_COST_PER_1M_USD: ${BETTY_DEEPSEEK_CHAT_OUTPUT_COST_PER_1M_USD:-1.10}\n BETTY_DEEPSEEK_REASONER_INPUT_COST_PER_1M_USD: ${BETTY_DEEPSEEK_REASONER_INPUT_COST_PER_1M_USD:-0.55}\n BETTY_DEEPSEEK_REASONER_OUTPUT_COST_PER_1M_USD: ${BETTY_DEEPSEEK_REASONER_OUTPUT_COST_PER_1M_USD:-2.19}\n KOKORO_ENABLED: ${KOKORO_ENABLED:-true}\n KOKORO_DEFAULT_VOICE: ${KOKORO_DEFAULT_VOICE:-af_bella}\n BETTY_OPERATOR_KOKORO_SPEED: ${BETTY_OPERATOR_KOKORO_SPEED:-1.0}\n BETTY_OPERATOR_KOKORO_DATA_DIR: ${BETTY_OPERATOR_KOKORO_DATA_DIR:-/app/data/kokoro}\n ELEVENLABS_ENABLED: ${ELEVENLABS_ENABLED:-true}\n TWILIO_ENABLED: ${TWILIO_ENABLED:-true}\n VAPI_ENABLED: ${VAPI_ENABLED:-true}\n BETTY_COST_DAILY_CAP_USD: ${BETTY_COST_DAILY_CAP_USD:-5}\n BETTY_COST_MONTHLY_CAP_USD: ${BETTY_COST_MONTHLY_CAP_USD:-50}\n BETTY_COST_WARNING_RATIO: ${BETTY_COST_WARNING_RATIO:-0.8}\n BETTY_ELEVENLABS_COST_PER_1K_CHARS_USD: ${BETTY_ELEVENLABS_COST_PER_1K_CHARS_USD:-0.30}\n BETTY_TWILIO_SMS_COST_USD: ${BETTY_TWILIO_SMS_COST_USD:-0.008}\n BETTY_VAPI_COST_PER_MINUTE_USD: ${BETTY_VAPI_COST_PER_MINUTE_USD:-0.075}\n ELEVENLABS_API_KEY: ${ELEVENLABS_API_KEY:-}\n ELEVENLABS_VOICE_ID: ${ELEVENLABS_VOICE_ID:-4tRn1lSkEn13EVTuqb0g}\n ELEVENLABS_MODEL_ID: ${ELEVENLABS_MODEL_ID:-eleven_turbo_v2_5}\n PIPER_VOICE_MODEL: ${PIPER_VOICE_MODEL:-en_US-lessac-medium}\n BETTY_OPERATOR_PIPER_DATA_DIR: ${BETTY_OPERATOR_PIPER_DATA_DIR:-/app/data/piper}\n GOOGLE_CALENDAR_CLIENT_ID: ${GOOGLE_CALENDAR_CLIENT_ID:-}\n GOOGLE_CALENDAR_CLIENT_SECRET: ${GOOGLE_CALENDAR_CLIENT_SECRET:-}\n GOOGLE_CALENDAR_OAUTH_REDIRECT_URI: ${GOOGLE_CALENDAR_OAUTH_REDIRECT_URI:-}\n BETTY_OPERATOR_RATE_LIMIT_WINDOW_SECONDS: ${BETTY_OPERATOR_RATE_LIMIT_WINDOW_SECONDS:-60}\n BETTY_OPERATOR_ELEVENLABS_RATE_LIMIT: ${BETTY_OPERATOR_ELEVENLABS_RATE_LIMIT:-20}\n BETTY_OPERATOR_TWILIO_RATE_LIMIT: ${BETTY_OPERATOR_TWILIO_RATE_LIMIT:-20}\n VAPI_API_KEY: ${VAPI_API_KEY:-}\n VAPI_ASSISTANT_ID: ${VAPI_ASSISTANT_ID:-}\n VAPI_WEBHOOK_SECRET: ${VAPI_WEBHOOK_SECRET:-}\n TWILIO_ACCOUNT_SID: ${TWILIO_ACCOUNT_SID:-}\n TWILIO_AUTH_TOKEN: ${TWILIO_AUTH_TOKEN:-}\n TWILIO_PHONE_NUMBER: ${TWILIO_PHONE_NUMBER:-+19492391845}\n ports:\n - \"0.0.0.0:${BETTY_OPERATOR_HOST_PORT:-18120}:8000\"\n volumes:\n - betty_operator_data:/app/data\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 15s\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n n8n:\n image: n8nio/n8n:latest\n container_name: betty-n8n\n depends_on:\n postgres:\n condition: service_healthy\n ollama:\n condition: service_healthy\n ollama-wrapper:\n condition: service_healthy\n betty-memory:\n condition: service_healthy\n betty-actions:\n condition: service_healthy\n betty-lookup:\n condition: service_healthy\n betty-operator:\n condition: service_healthy\n environment:\n N8N_HOST: ${N8N_HOST:-localhost}\n N8N_PORT: 5678\n N8N_PROTOCOL: ${N8N_PROTOCOL:-http}\n WEBHOOK_URL: ${WEBHOOK_URL:-http://localhost:15678/}\n GENERIC_TIMEZONE: ${GENERIC_TIMEZONE:-America/Los_Angeles}\n N8N_ENCRYPTION_KEY: ${N8N_ENCRYPTION_KEY:?set N8N_ENCRYPTION_KEY in .env}\n N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS: \"true\"\n N8N_DIAGNOSTICS_ENABLED: \"false\"\n N8N_PERSONALIZATION_ENABLED: \"false\"\n N8N_SECURE_COOKIE: \"false\"\n N8N_BASIC_AUTH_ACTIVE: ${N8N_BASIC_AUTH_ACTIVE:-true}\n N8N_BASIC_AUTH_USER: ${N8N_BASIC_AUTH_USER:?set N8N_BASIC_AUTH_USER in .env}\n N8N_BASIC_AUTH_PASSWORD: ${N8N_BASIC_AUTH_PASSWORD:?set N8N_BASIC_AUTH_PASSWORD in .env}\n DB_TYPE: postgresdb\n DB_POSTGRESDB_HOST: postgres\n DB_POSTGRESDB_PORT: 5432\n DB_POSTGRESDB_DATABASE: ${POSTGRES_DB:-n8n}\n DB_POSTGRESDB_USER: ${POSTGRES_USER:-n8n}\n DB_POSTGRESDB_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n OLLAMA_WRAPPER_URL: http://ollama-wrapper:8000\n BETTY_MEMORY_URL: http://betty-memory:8000\n BETTY_ACTIONS_URL: http://betty-actions:8000\n BETTY_LOOKUP_URL: http://betty-lookup:8000\n BETTY_OPERATOR_URL: http://betty-operator:8000\n ports:\n - \"127.0.0.1:${N8N_HOST_PORT:-15678}:5678\"\n volumes:\n - n8n_data:/home/node/.n8n\n restart: unless-stopped\n security_opt:\n - no-new-privileges:true\n networks:\n - betty-net\n\nvolumes:\n postgres_data:\n chroma_data:\n ollama_data:\n betty_operator_data:\n n8n_data:\n\nnetworks:\n betty-net:\n driver: bridge\n"}, "6ffea95553020c9b0114a3f1a9c0a89d877be2d9": {"t": 1785769591.724989, "n": 19275, "h": "46446bf603617b95", "c": "name: betty-stack\n\nservices:\n postgres:\n image: postgres:16-alpine\n container_name: betty-postgres\n environment:\n POSTGRES_USER: ${POSTGRES_USER:-n8n}\n POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n POSTGRES_DB: ${POSTGRES_DB:-n8n}\n volumes:\n - postgres_data:/var/lib/postgresql/data\n healthcheck:\n test: [\"CMD-SHELL\", \"pg_isready -U ${POSTGRES_USER:-n8n} -d ${POSTGRES_DB:-n8n}\"]\n interval: 10s\n timeout: 5s\n retries: 10\n start_period: 15s\n restart: unless-stopped\n security_opt:\n - no-new-privileges:true\n networks:\n - betty-net\n\n chroma:\n image: chromadb/chroma:0.5.23\n container_name: betty-chroma\n volumes:\n - chroma_data:/chroma/chroma\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/api/v1/heartbeat', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 15s\n restart: unless-stopped\n security_opt:\n - no-new-privileges:true\n networks:\n - betty-net\n\n ollama:\n image: ollama/ollama:latest\n container_name: betty-ollama\n ports:\n - \"127.0.0.1:${OLLAMA_HOST_PORT:-11435}:11434\"\n volumes:\n - ollama_data:/root/.ollama\n healthcheck:\n test: [\"CMD-SHELL\", \"ollama list >/dev/null 2>&1 || exit 1\"]\n interval: 20s\n timeout: 10s\n retries: 6\n start_period: 30s\n restart: unless-stopped\n security_opt:\n - no-new-privileges:true\n networks:\n - betty-net\n\n ollama-wrapper:\n build:\n context: ./ollama-wrapper\n image: betty/ollama-wrapper:local\n container_name: betty-ollama-wrapper\n depends_on:\n ollama:\n condition: service_healthy\n environment:\n OLLAMA_URL: http://ollama:11434\n API_USERNAME: ${WRAPPER_API_USERNAME:?set WRAPPER_API_USERNAME in .env}\n API_PASSWORD: ${WRAPPER_API_PASSWORD:?set WRAPPER_API_PASSWORD in .env}\n API_BEARER_TOKEN: ${WRAPPER_API_BEARER_TOKEN:-}\n WRAPPER_REQUEST_TIMEOUT_SECONDS: ${WRAPPER_REQUEST_TIMEOUT_SECONDS:-120}\n LOG_LEVEL: ${WRAPPER_LOG_LEVEL:-info}\n ports:\n - \"127.0.0.1:${WRAPPER_HOST_PORT:-18000}:8000\"\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 10s\n read_only: true\n tmpfs:\n - /tmp\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n betty-memory:\n build:\n context: ./services/memory\n image: betty/memory-service:local\n container_name: betty-memory\n depends_on:\n postgres:\n condition: service_healthy\n chroma:\n condition: service_healthy\n ollama:\n condition: service_healthy\n environment:\n BETTY_MEMORY_POSTGRES_HOST: postgres\n BETTY_MEMORY_POSTGRES_PORT: 5432\n BETTY_MEMORY_POSTGRES_DB: ${POSTGRES_DB:-n8n}\n BETTY_MEMORY_POSTGRES_USER: ${POSTGRES_USER:-n8n}\n BETTY_MEMORY_POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n BETTY_MEMORY_CHROMA_HOST: chroma\n BETTY_MEMORY_CHROMA_PORT: 8000\n BETTY_MEMORY_COLLECTION: ${BETTY_MEMORY_COLLECTION:-betty-v1-facts}\n BETTY_MEMORY_OLLAMA_URL: http://ollama:11434\n BETTY_MEMORY_EMBED_MODEL: ${BETTY_MEMORY_EMBED_MODEL:-nomic-embed-text}\n BETTY_MEMORY_SEMANTIC_DISTANCE_THRESHOLD: ${BETTY_MEMORY_SEMANTIC_DISTANCE_THRESHOLD:-0.45}\n BETTY_MEMORY_LOG_LEVEL: ${BETTY_MEMORY_LOG_LEVEL:-info}\n BETTY_MEMORY_DEFAULT_TIMEZONE: ${GENERIC_TIMEZONE:-America/Los_Angeles}\n BETTY_OWNER_NAME: ${BETTY_OWNER_NAME:-Vincent}\n BETTY_HOME_CITY: ${BETTY_HOME_CITY:-Corona Del Mar, CA}\n ports:\n - \"127.0.0.1:${BETTY_MEMORY_HOST_PORT:-18100}:8000\"\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 15s\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n betty-actions:\n build:\n context: ./services/actions\n image: betty/actions-service:local\n container_name: betty-actions\n depends_on:\n postgres:\n condition: service_healthy\n environment:\n BETTY_ACTIONS_POSTGRES_HOST: postgres\n BETTY_ACTIONS_POSTGRES_PORT: 5432\n BETTY_ACTIONS_POSTGRES_DB: ${POSTGRES_DB:-n8n}\n BETTY_ACTIONS_POSTGRES_USER: ${POSTGRES_USER:-n8n}\n BETTY_ACTIONS_POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n BETTY_ACTIONS_DEFAULT_TIMEZONE: ${GENERIC_TIMEZONE:-America/Los_Angeles}\n GOOGLE_CALENDAR_CLIENT_ID: ${GOOGLE_CALENDAR_CLIENT_ID:-}\n GOOGLE_CALENDAR_CLIENT_SECRET: ${GOOGLE_CALENDAR_CLIENT_SECRET:-}\n ports:\n - \"127.0.0.1:${BETTY_ACTIONS_HOST_PORT:-18110}:8000\"\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 15s\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n betty-lookup:\n build:\n context: ./services/lookup\n image: betty/lookup-service:local\n container_name: betty-lookup\n depends_on:\n postgres:\n condition: service_healthy\n ollama-wrapper:\n condition: service_healthy\n environment:\n BETTY_LOOKUP_POSTGRES_HOST: postgres\n BETTY_LOOKUP_POSTGRES_PORT: 5432\n BETTY_LOOKUP_POSTGRES_DB: ${POSTGRES_DB:-n8n}\n BETTY_LOOKUP_POSTGRES_USER: ${POSTGRES_USER:-n8n}\n BETTY_LOOKUP_POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n BETTY_LOOKUP_DEFAULT_TIMEZONE: ${GENERIC_TIMEZONE:-America/Los_Angeles}\n BETTY_OWNER_NAME: ${BETTY_OWNER_NAME:-Vincent}\n BETTY_HOME_CITY: ${BETTY_HOME_CITY:-Corona Del Mar, CA}\n BRAVE_ENABLED: ${BRAVE_ENABLED:-false}\n SEARXNG_ENABLED: ${SEARXNG_ENABLED:-true}\n SEARXNG_BASE_URL: ${SEARXNG_BASE_URL:-http://host.docker.internal:8080}\n BETTY_COST_DAILY_CAP_USD: ${BETTY_COST_DAILY_CAP_USD:-5}\n BETTY_COST_MONTHLY_CAP_USD: ${BETTY_COST_MONTHLY_CAP_USD:-50}\n BETTY_COST_WARNING_RATIO: ${BETTY_COST_WARNING_RATIO:-0.8}\n BRAVE_SEARCH_API_KEY: ${BRAVE_SEARCH_API_KEY:-}\n BETTY_LOOKUP_WRAPPER_URL: http://ollama-wrapper:8000/v1/chat/completions\n BETTY_LOOKUP_WRAPPER_USERNAME: ${WRAPPER_API_USERNAME:?set WRAPPER_API_USERNAME in .env}\n BETTY_LOOKUP_WRAPPER_PASSWORD: ${WRAPPER_API_PASSWORD:?set WRAPPER_API_PASSWORD in .env}\n BETTY_LOOKUP_WRAPPER_BEARER_TOKEN: ${WRAPPER_API_BEARER_TOKEN:-}\n BETTY_LOOKUP_MODEL: ${BETTY_LOOKUP_MODEL:-}\n BETTY_LOOKUP_WRAPPER_TIMEOUT_SECONDS: ${WRAPPER_REQUEST_TIMEOUT_SECONDS:-120}\n ports:\n - \"127.0.0.1:${BETTY_LOOKUP_HOST_PORT:-18130}:8000\"\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 15s\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n betty-brain:\n build:\n context: ./services/brain\n image: betty/brain-service:local\n container_name: betty-brain\n depends_on:\n postgres:\n condition: service_healthy\n betty-memory:\n condition: service_healthy\n betty-lookup:\n condition: service_healthy\n environment:\n BETTY_BRAIN_POSTGRES_HOST: postgres\n BETTY_BRAIN_POSTGRES_PORT: 5432\n BETTY_BRAIN_POSTGRES_DB: ${POSTGRES_DB:-n8n}\n BETTY_BRAIN_POSTGRES_USER: ${POSTGRES_USER:-n8n}\n BETTY_BRAIN_POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n BETTY_BRAIN_MODEL: ${BETTY_BRAIN_MODEL:-claude-haiku-4-5}\n BETTY_BRAIN_MAX_TOKENS: ${BETTY_BRAIN_MAX_TOKENS:-2000}\n BETTY_BRAIN_MAX_AGENT_ITERATIONS: ${BETTY_BRAIN_MAX_AGENT_ITERATIONS:-8}\n BETTY_BRAIN_MEMORY_URL: http://betty-memory:8000\n BETTY_BRAIN_LOOKUP_URL: http://betty-lookup:8000\n BETTY_BRAIN_TOOL_HTTP_TIMEOUT: ${BETTY_BRAIN_TOOL_HTTP_TIMEOUT:-30}\n BETTY_BRAIN_LOOKUP_HTTP_TIMEOUT: ${BETTY_BRAIN_LOOKUP_HTTP_TIMEOUT:-60}\n BETTY_SLACK_BOT_TOKEN: ${BETTY_SLACK_BOT_TOKEN:-}\n BETTY_SLACK_DEFAULT_DELEGATION_CHANNEL: ${BETTY_SLACK_DEFAULT_DELEGATION_CHANNEL:-#betty-station}\n ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:?set ANTHROPIC_API_KEY in .env}\n BETTY_COST_DAILY_CAP_USD: ${BETTY_COST_DAILY_CAP_USD:-1}\n BETTY_COST_MONTHLY_CAP_USD: ${BETTY_COST_MONTHLY_CAP_USD:-30}\n BETTY_COST_WARNING_RATIO: ${BETTY_COST_WARNING_RATIO:-0.8}\n BETTY_OWNER_NAME: ${BETTY_OWNER_NAME:-Vincent}\n BETTY_HOME_CITY: ${BETTY_HOME_CITY:-Rancho Santa Margarita, CA}\n ports:\n - \"127.0.0.1:${BETTY_BRAIN_HOST_PORT:-18140}:8000\"\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 15s\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n betty-daemon:\n build:\n context: ./services/betty-slack-daemon\n image: betty/betty-slack-daemon:local\n container_name: betty-daemon\n depends_on:\n betty-brain:\n condition: service_healthy\n betty-memory:\n condition: service_healthy\n environment:\n BETTY_SLACK_APP_TOKEN: ${BETTY_SLACK_APP_TOKEN:?set BETTY_SLACK_APP_TOKEN in .env after creating Betty's Slack app}\n BETTY_SLACK_BOT_TOKEN: ${BETTY_SLACK_BOT_TOKEN:?set BETTY_SLACK_BOT_TOKEN in .env after creating Betty's Slack app}\n BETTY_BRAIN_URL: http://betty-brain:8000\n BETTY_BRAIN_TIMEOUT_SECONDS: ${BETTY_BRAIN_TIMEOUT_SECONDS:-60}\n BETTY_DAEMON_MEMORY_URL: http://betty-memory:8000\n BETTY_DAEMON_MEMORY_TIMEOUT_SECONDS: ${BETTY_DAEMON_MEMORY_TIMEOUT_SECONDS:-15}\n BETTY_OWNER_SLACK_USER_ID: ${BETTY_OWNER_SLACK_USER_ID:-}\n BETTY_DAEMON_REMINDER_POLL_INTERVAL_SECONDS: ${BETTY_DAEMON_REMINDER_POLL_INTERVAL_SECONDS:-60}\n BETTY_DAEMON_REMINDER_LOOKAHEAD_SECONDS: ${BETTY_DAEMON_REMINDER_LOOKAHEAD_SECONDS:-60}\n BETTY_DAEMON_DATA_DIR: /data\n BETTY_DAEMON_THREAD_KEEP_MSGS: ${BETTY_DAEMON_THREAD_KEEP_MSGS:-20}\n volumes:\n - ./data/betty-daemon:/data\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n betty-operator:\n build:\n context: ./services/operator\n image: betty/operator-service:local\n container_name: betty-operator\n depends_on:\n postgres:\n condition: service_healthy\n betty-memory:\n condition: service_healthy\n betty-actions:\n condition: service_healthy\n betty-lookup:\n condition: service_healthy\n environment:\n BETTY_OPERATOR_POSTGRES_HOST: postgres\n BETTY_OPERATOR_POSTGRES_PORT: 5432\n BETTY_OPERATOR_POSTGRES_DB: ${POSTGRES_DB:-n8n}\n BETTY_OPERATOR_POSTGRES_USER: ${POSTGRES_USER:-n8n}\n BETTY_OPERATOR_POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n BETTY_OPERATOR_MEMORY_URL: http://betty-memory:8000\n BETTY_OPERATOR_ACTIONS_URL: http://betty-actions:8000\n BETTY_OPERATOR_LOOKUP_URL: http://betty-lookup:8000\n BETTY_OPERATOR_WRAPPER_URL: http://ollama-wrapper:8000/v1/chat/completions\n BETTY_OPERATOR_WRAPPER_USERNAME: ${WRAPPER_API_USERNAME:?set WRAPPER_API_USERNAME in .env}\n BETTY_OPERATOR_WRAPPER_PASSWORD: ${WRAPPER_API_PASSWORD:?set WRAPPER_API_PASSWORD in .env}\n BETTY_OPERATOR_WRAPPER_BEARER_TOKEN: ${WRAPPER_API_BEARER_TOKEN:-}\n BETTY_OPERATOR_WRAPPER_TIMEOUT_SECONDS: ${WRAPPER_REQUEST_TIMEOUT_SECONDS:-120}\n BETTY_OPERATOR_DEFAULT_TIMEZONE: ${GENERIC_TIMEZONE:-America/Los_Angeles}\n BETTY_PUBLIC_BASE_URL: ${BETTY_PUBLIC_BASE_URL:-https://YOUR_PUBLIC_URL}\n BETTY_OWNER_PHONE: ${BETTY_OWNER_PHONE:-}\n BETTY_OWNER_NAME: ${BETTY_OWNER_NAME:-Vincent}\n BETTY_HOME_CITY: ${BETTY_HOME_CITY:-Corona Del Mar, CA}\n BETTY_OPERATOR_STT_PROVIDER: ${BETTY_OPERATOR_STT_PROVIDER:-whisper_local}\n BETTY_OPERATOR_WHISPER_MODEL: ${BETTY_OPERATOR_WHISPER_MODEL:-base.en}\n BETTY_OPERATOR_WHISPER_DEVICE: ${BETTY_OPERATOR_WHISPER_DEVICE:-cpu}\n BETTY_OPERATOR_WHISPER_COMPUTE_TYPE: ${BETTY_OPERATOR_WHISPER_COMPUTE_TYPE:-int8}\n BETTY_OPERATOR_WHISPER_DATA_DIR: ${BETTY_OPERATOR_WHISPER_DATA_DIR:-/app/data/whisper}\n BETTY_MODEL_MODE: ${BETTY_MODEL_MODE:-default}\n BETTY_DEFAULT_CHAT_PROVIDER: ${BETTY_DEFAULT_CHAT_PROVIDER:-auto}\n BETTY_DEFAULT_CHAT_MODEL: ${BETTY_DEFAULT_CHAT_MODEL:-deepseek-chat}\n BETTY_DEFAULT_LOCAL_CHAT_MODEL: ${BETTY_DEFAULT_LOCAL_CHAT_MODEL:-llama3.1:8b}\n BETTY_MODEL_DOLPHIN: ${BETTY_MODEL_DOLPHIN:-dolphin-llama3:70b}\n BETTY_MODEL_MEDICAL: ${BETTY_MODEL_MEDICAL:-medllama2}\n BETTY_MODEL_MUSIC: ${BETTY_MODEL_MUSIC:-llamusic/llamusic}\n BETTY_MODEL_IMAGE: ${BETTY_MODEL_IMAGE:-llava:34b}\n BETTY_MODEL_WRITE: ${BETTY_MODEL_WRITE:-HammerAI/mythomax-l2}\n BETTY_MODEL_CODE: ${BETTY_MODEL_CODE:-qwen2.5-coder:32b}\n BETTY_MODEL_RESEARCH: ${BETTY_MODEL_RESEARCH:-nemotron}\n BETTY_MODEL_FAST: ${BETTY_MODEL_FAST:-gemma2:27b}\n DEEPSEEK_API_KEY: ${DEEPSEEK_API_KEY:-}\n DEEPSEEK_BASE_URL: ${DEEPSEEK_BASE_URL:-https://api.deepseek.com}\n BETTY_DEEPSEEK_MODEL: ${BETTY_DEEPSEEK_MODEL:-deepseek-chat}\n BETTY_DEEPSEEK_REASONER_MODEL: ${BETTY_DEEPSEEK_REASONER_MODEL:-deepseek-reasoner}\n BETTY_DEEPSEEK_TIMEOUT_SECONDS: ${BETTY_DEEPSEEK_TIMEOUT_SECONDS:-120}\n BETTY_DEEPSEEK_CHAT_INPUT_COST_PER_1M_USD: ${BETTY_DEEPSEEK_CHAT_INPUT_COST_PER_1M_USD:-0.27}\n BETTY_DEEPSEEK_CHAT_OUTPUT_COST_PER_1M_USD: ${BETTY_DEEPSEEK_CHAT_OUTPUT_COST_PER_1M_USD:-1.10}\n BETTY_DEEPSEEK_REASONER_INPUT_COST_PER_1M_USD: ${BETTY_DEEPSEEK_REASONER_INPUT_COST_PER_1M_USD:-0.55}\n BETTY_DEEPSEEK_REASONER_OUTPUT_COST_PER_1M_USD: ${BETTY_DEEPSEEK_REASONER_OUTPUT_COST_PER_1M_USD:-2.19}\n KOKORO_ENABLED: ${KOKORO_ENABLED:-true}\n KOKORO_DEFAULT_VOICE: ${KOKORO_DEFAULT_VOICE:-af_bella}\n BETTY_OPERATOR_KOKORO_SPEED: ${BETTY_OPERATOR_KOKORO_SPEED:-1.0}\n BETTY_OPERATOR_KOKORO_DATA_DIR: ${BETTY_OPERATOR_KOKORO_DATA_DIR:-/app/data/kokoro}\n ELEVENLABS_ENABLED: ${ELEVENLABS_ENABLED:-true}\n TWILIO_ENABLED: ${TWILIO_ENABLED:-true}\n VAPI_ENABLED: ${VAPI_ENABLED:-true}\n BETTY_COST_DAILY_CAP_USD: ${BETTY_COST_DAILY_CAP_USD:-5}\n BETTY_COST_MONTHLY_CAP_USD: ${BETTY_COST_MONTHLY_CAP_USD:-50}\n BETTY_COST_WARNING_RATIO: ${BETTY_COST_WARNING_RATIO:-0.8}\n BETTY_ELEVENLABS_COST_PER_1K_CHARS_USD: ${BETTY_ELEVENLABS_COST_PER_1K_CHARS_USD:-0.30}\n BETTY_TWILIO_SMS_COST_USD: ${BETTY_TWILIO_SMS_COST_USD:-0.008}\n BETTY_VAPI_COST_PER_MINUTE_USD: ${BETTY_VAPI_COST_PER_MINUTE_USD:-0.075}\n ELEVENLABS_API_KEY: ${ELEVENLABS_API_KEY:-}\n ELEVENLABS_VOICE_ID: ${ELEVENLABS_VOICE_ID:-4tRn1lSkEn13EVTuqb0g}\n ELEVENLABS_MODEL_ID: ${ELEVENLABS_MODEL_ID:-eleven_turbo_v2_5}\n PIPER_VOICE_MODEL: ${PIPER_VOICE_MODEL:-en_US-lessac-medium}\n BETTY_OPERATOR_PIPER_DATA_DIR: ${BETTY_OPERATOR_PIPER_DATA_DIR:-/app/data/piper}\n GOOGLE_CALENDAR_CLIENT_ID: ${GOOGLE_CALENDAR_CLIENT_ID:-}\n GOOGLE_CALENDAR_CLIENT_SECRET: ${GOOGLE_CALENDAR_CLIENT_SECRET:-}\n GOOGLE_CALENDAR_OAUTH_REDIRECT_URI: ${GOOGLE_CALENDAR_OAUTH_REDIRECT_URI:-}\n BETTY_OPERATOR_RATE_LIMIT_WINDOW_SECONDS: ${BETTY_OPERATOR_RATE_LIMIT_WINDOW_SECONDS:-60}\n BETTY_OPERATOR_ELEVENLABS_RATE_LIMIT: ${BETTY_OPERATOR_ELEVENLABS_RATE_LIMIT:-20}\n BETTY_OPERATOR_TWILIO_RATE_LIMIT: ${BETTY_OPERATOR_TWILIO_RATE_LIMIT:-20}\n VAPI_API_KEY: ${VAPI_API_KEY:-}\n VAPI_ASSISTANT_ID: ${VAPI_ASSISTANT_ID:-}\n VAPI_WEBHOOK_SECRET: ${VAPI_WEBHOOK_SECRET:-}\n TWILIO_ACCOUNT_SID: ${TWILIO_ACCOUNT_SID:-}\n TWILIO_AUTH_TOKEN: ${TWILIO_AUTH_TOKEN:-}\n TWILIO_PHONE_NUMBER: ${TWILIO_PHONE_NUMBER:-+19492391845}\n ports:\n - \"0.0.0.0:${BETTY_OPERATOR_HOST_PORT:-18120}:8000\"\n volumes:\n - betty_operator_data:/app/data\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 15s\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n n8n:\n image: n8nio/n8n:latest\n container_name: betty-n8n\n depends_on:\n postgres:\n condition: service_healthy\n ollama:\n condition: service_healthy\n ollama-wrapper:\n condition: service_healthy\n betty-memory:\n condition: service_healthy\n betty-actions:\n condition: service_healthy\n betty-lookup:\n condition: service_healthy\n betty-operator:\n condition: service_healthy\n environment:\n N8N_HOST: ${N8N_HOST:-localhost}\n N8N_PORT: 5678\n N8N_PROTOCOL: ${N8N_PROTOCOL:-http}\n WEBHOOK_URL: ${WEBHOOK_URL:-http://localhost:15678/}\n GENERIC_TIMEZONE: ${GENERIC_TIMEZONE:-America/Los_Angeles}\n N8N_ENCRYPTION_KEY: ${N8N_ENCRYPTION_KEY:?set N8N_ENCRYPTION_KEY in .env}\n N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS: \"true\"\n N8N_DIAGNOSTICS_ENABLED: \"false\"\n N8N_PERSONALIZATION_ENABLED: \"false\"\n N8N_SECURE_COOKIE: \"false\"\n N8N_BASIC_AUTH_ACTIVE: ${N8N_BASIC_AUTH_ACTIVE:-true}\n N8N_BASIC_AUTH_USER: ${N8N_BASIC_AUTH_USER:?set N8N_BASIC_AUTH_USER in .env}\n N8N_BASIC_AUTH_PASSWORD: ${N8N_BASIC_AUTH_PASSWORD:?set N8N_BASIC_AUTH_PASSWORD in .env}\n DB_TYPE: postgresdb\n DB_POSTGRESDB_HOST: postgres\n DB_POSTGRESDB_PORT: 5432\n DB_POSTGRESDB_DATABASE: ${POSTGRES_DB:-n8n}\n DB_POSTGRESDB_USER: ${POSTGRES_USER:-n8n}\n DB_POSTGRESDB_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n OLLAMA_WRAPPER_URL: http://ollama-wrapper:8000\n BETTY_MEMORY_URL: http://betty-memory:8000\n BETTY_ACTIONS_URL: http://betty-actions:8000\n BETTY_LOOKUP_URL: http://betty-lookup:8000\n BETTY_OPERATOR_URL: http://betty-operator:8000\n ports:\n - \"127.0.0.1:${N8N_HOST_PORT:-15678}:5678\"\n volumes:\n - n8n_data:/home/node/.n8n\n restart: unless-stopped\n security_opt:\n - no-new-privileges:true\n networks:\n - betty-net\n\nvolumes:\n postgres_data:\n chroma_data:\n ollama_data:\n betty_operator_data:\n n8n_data:\n\nnetworks:\n betty-net:\n driver: bridge\n"}, "1e566827119fdf4cba567432ddb0346fb7d10017": {"t": 1785769593.6904247, "n": 18818, "h": "c21bc78fedefd3c2", "c": "name: betty-stack\n\nservices:\n postgres:\n image: postgres:16-alpine\n container_name: betty-postgres\n environment:\n POSTGRES_USER: ${POSTGRES_USER:-n8n}\n POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n POSTGRES_DB: ${POSTGRES_DB:-n8n}\n volumes:\n - postgres_data:/var/lib/postgresql/data\n healthcheck:\n test: [\"CMD-SHELL\", \"pg_isready -U ${POSTGRES_USER:-n8n} -d ${POSTGRES_DB:-n8n}\"]\n interval: 10s\n timeout: 5s\n retries: 10\n start_period: 15s\n restart: unless-stopped\n security_opt:\n - no-new-privileges:true\n networks:\n - betty-net\n\n chroma:\n image: chromadb/chroma:0.5.23\n container_name: betty-chroma\n volumes:\n - chroma_data:/chroma/chroma\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/api/v1/heartbeat', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 15s\n restart: unless-stopped\n security_opt:\n - no-new-privileges:true\n networks:\n - betty-net\n\n ollama:\n image: ollama/ollama:latest\n container_name: betty-ollama\n ports:\n - \"127.0.0.1:${OLLAMA_HOST_PORT:-11435}:11434\"\n volumes:\n - ollama_data:/root/.ollama\n healthcheck:\n test: [\"CMD-SHELL\", \"ollama list >/dev/null 2>&1 || exit 1\"]\n interval: 20s\n timeout: 10s\n retries: 6\n start_period: 30s\n restart: unless-stopped\n security_opt:\n - no-new-privileges:true\n networks:\n - betty-net\n\n ollama-wrapper:\n build:\n context: ./ollama-wrapper\n image: betty/ollama-wrapper:local\n container_name: betty-ollama-wrapper\n depends_on:\n ollama:\n condition: service_healthy\n environment:\n OLLAMA_URL: http://ollama:11434\n API_USERNAME: ${WRAPPER_API_USERNAME:?set WRAPPER_API_USERNAME in .env}\n API_PASSWORD: ${WRAPPER_API_PASSWORD:?set WRAPPER_API_PASSWORD in .env}\n API_BEARER_TOKEN: ${WRAPPER_API_BEARER_TOKEN:-}\n WRAPPER_REQUEST_TIMEOUT_SECONDS: ${WRAPPER_REQUEST_TIMEOUT_SECONDS:-120}\n LOG_LEVEL: ${WRAPPER_LOG_LEVEL:-info}\n ports:\n - \"127.0.0.1:${WRAPPER_HOST_PORT:-18000}:8000\"\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 10s\n read_only: true\n tmpfs:\n - /tmp\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n betty-memory:\n build:\n context: ./services/memory\n image: betty/memory-service:local\n container_name: betty-memory\n depends_on:\n postgres:\n condition: service_healthy\n chroma:\n condition: service_healthy\n ollama:\n condition: service_healthy\n environment:\n BETTY_MEMORY_POSTGRES_HOST: postgres\n BETTY_MEMORY_POSTGRES_PORT: 5432\n BETTY_MEMORY_POSTGRES_DB: ${POSTGRES_DB:-n8n}\n BETTY_MEMORY_POSTGRES_USER: ${POSTGRES_USER:-n8n}\n BETTY_MEMORY_POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n BETTY_MEMORY_CHROMA_HOST: chroma\n BETTY_MEMORY_CHROMA_PORT: 8000\n BETTY_MEMORY_COLLECTION: ${BETTY_MEMORY_COLLECTION:-betty-v1-facts}\n BETTY_MEMORY_OLLAMA_URL: http://ollama:11434\n BETTY_MEMORY_EMBED_MODEL: ${BETTY_MEMORY_EMBED_MODEL:-nomic-embed-text}\n BETTY_MEMORY_SEMANTIC_DISTANCE_THRESHOLD: ${BETTY_MEMORY_SEMANTIC_DISTANCE_THRESHOLD:-0.45}\n BETTY_MEMORY_LOG_LEVEL: ${BETTY_MEMORY_LOG_LEVEL:-info}\n BETTY_MEMORY_DEFAULT_TIMEZONE: ${GENERIC_TIMEZONE:-America/Los_Angeles}\n BETTY_OWNER_NAME: ${BETTY_OWNER_NAME:-Vincent}\n BETTY_HOME_CITY: ${BETTY_HOME_CITY:-Corona Del Mar, CA}\n ports:\n - \"127.0.0.1:${BETTY_MEMORY_HOST_PORT:-18100}:8000\"\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 15s\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n betty-actions:\n build:\n context: ./services/actions\n image: betty/actions-service:local\n container_name: betty-actions\n depends_on:\n postgres:\n condition: service_healthy\n environment:\n BETTY_ACTIONS_POSTGRES_HOST: postgres\n BETTY_ACTIONS_POSTGRES_PORT: 5432\n BETTY_ACTIONS_POSTGRES_DB: ${POSTGRES_DB:-n8n}\n BETTY_ACTIONS_POSTGRES_USER: ${POSTGRES_USER:-n8n}\n BETTY_ACTIONS_POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n BETTY_ACTIONS_DEFAULT_TIMEZONE: ${GENERIC_TIMEZONE:-America/Los_Angeles}\n GOOGLE_CALENDAR_CLIENT_ID: ${GOOGLE_CALENDAR_CLIENT_ID:-}\n GOOGLE_CALENDAR_CLIENT_SECRET: ${GOOGLE_CALENDAR_CLIENT_SECRET:-}\n ports:\n - \"127.0.0.1:${BETTY_ACTIONS_HOST_PORT:-18110}:8000\"\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 15s\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n betty-lookup:\n build:\n context: ./services/lookup\n image: betty/lookup-service:local\n container_name: betty-lookup\n depends_on:\n postgres:\n condition: service_healthy\n ollama-wrapper:\n condition: service_healthy\n environment:\n BETTY_LOOKUP_POSTGRES_HOST: postgres\n BETTY_LOOKUP_POSTGRES_PORT: 5432\n BETTY_LOOKUP_POSTGRES_DB: ${POSTGRES_DB:-n8n}\n BETTY_LOOKUP_POSTGRES_USER: ${POSTGRES_USER:-n8n}\n BETTY_LOOKUP_POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n BETTY_LOOKUP_DEFAULT_TIMEZONE: ${GENERIC_TIMEZONE:-America/Los_Angeles}\n BETTY_OWNER_NAME: ${BETTY_OWNER_NAME:-Vincent}\n BETTY_HOME_CITY: ${BETTY_HOME_CITY:-Corona Del Mar, CA}\n BRAVE_ENABLED: ${BRAVE_ENABLED:-false}\n SEARXNG_ENABLED: ${SEARXNG_ENABLED:-true}\n SEARXNG_BASE_URL: ${SEARXNG_BASE_URL:-http://host.docker.internal:8080}\n BETTY_COST_DAILY_CAP_USD: ${BETTY_COST_DAILY_CAP_USD:-5}\n BETTY_COST_MONTHLY_CAP_USD: ${BETTY_COST_MONTHLY_CAP_USD:-50}\n BETTY_COST_WARNING_RATIO: ${BETTY_COST_WARNING_RATIO:-0.8}\n BRAVE_SEARCH_API_KEY: ${BRAVE_SEARCH_API_KEY:-}\n BETTY_LOOKUP_WRAPPER_URL: http://ollama-wrapper:8000/v1/chat/completions\n BETTY_LOOKUP_WRAPPER_USERNAME: ${WRAPPER_API_USERNAME:?set WRAPPER_API_USERNAME in .env}\n BETTY_LOOKUP_WRAPPER_PASSWORD: ${WRAPPER_API_PASSWORD:?set WRAPPER_API_PASSWORD in .env}\n BETTY_LOOKUP_WRAPPER_BEARER_TOKEN: ${WRAPPER_API_BEARER_TOKEN:-}\n BETTY_LOOKUP_MODEL: ${BETTY_LOOKUP_MODEL:-}\n BETTY_LOOKUP_WRAPPER_TIMEOUT_SECONDS: ${WRAPPER_REQUEST_TIMEOUT_SECONDS:-120}\n ports:\n - \"127.0.0.1:${BETTY_LOOKUP_HOST_PORT:-18130}:8000\"\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 15s\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n betty-brain:\n build:\n context: ./services/brain\n image: betty/brain-service:local\n container_name: betty-brain\n depends_on:\n postgres:\n condition: service_healthy\n betty-memory:\n condition: service_healthy\n betty-lookup:\n condition: service_healthy\n environment:\n BETTY_BRAIN_POSTGRES_HOST: postgres\n BETTY_BRAIN_POSTGRES_PORT: 5432\n BETTY_BRAIN_POSTGRES_DB: ${POSTGRES_DB:-n8n}\n BETTY_BRAIN_POSTGRES_USER: ${POSTGRES_USER:-n8n}\n BETTY_BRAIN_POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n BETTY_BRAIN_MODEL: ${BETTY_BRAIN_MODEL:-claude-haiku-4-5}\n BETTY_BRAIN_MAX_TOKENS: ${BETTY_BRAIN_MAX_TOKENS:-2000}\n BETTY_BRAIN_MAX_AGENT_ITERATIONS: ${BETTY_BRAIN_MAX_AGENT_ITERATIONS:-8}\n BETTY_BRAIN_MEMORY_URL: http://betty-memory:8000\n BETTY_BRAIN_LOOKUP_URL: http://betty-lookup:8000\n BETTY_BRAIN_TOOL_HTTP_TIMEOUT: ${BETTY_BRAIN_TOOL_HTTP_TIMEOUT:-30}\n BETTY_BRAIN_LOOKUP_HTTP_TIMEOUT: ${BETTY_BRAIN_LOOKUP_HTTP_TIMEOUT:-60}\n BETTY_SLACK_BOT_TOKEN: ${BETTY_SLACK_BOT_TOKEN:-}\n BETTY_SLACK_DEFAULT_DELEGATION_CHANNEL: ${BETTY_SLACK_DEFAULT_DELEGATION_CHANNEL:-#betty-station}\n ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:?set ANTHROPIC_API_KEY in .env}\n BETTY_COST_DAILY_CAP_USD: ${BETTY_COST_DAILY_CAP_USD:-1}\n BETTY_COST_MONTHLY_CAP_USD: ${BETTY_COST_MONTHLY_CAP_USD:-30}\n BETTY_COST_WARNING_RATIO: ${BETTY_COST_WARNING_RATIO:-0.8}\n BETTY_OWNER_NAME: ${BETTY_OWNER_NAME:-Vincent}\n BETTY_HOME_CITY: ${BETTY_HOME_CITY:-Rancho Santa Margarita, CA}\n ports:\n - \"127.0.0.1:${BETTY_BRAIN_HOST_PORT:-18140}:8000\"\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 15s\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n betty-daemon:\n build:\n context: ./services/betty-slack-daemon\n image: betty/betty-slack-daemon:local\n container_name: betty-daemon\n depends_on:\n betty-brain:\n condition: service_healthy\n environment:\n BETTY_SLACK_APP_TOKEN: ${BETTY_SLACK_APP_TOKEN:?set BETTY_SLACK_APP_TOKEN in .env after creating Betty's Slack app}\n BETTY_SLACK_BOT_TOKEN: ${BETTY_SLACK_BOT_TOKEN:?set BETTY_SLACK_BOT_TOKEN in .env after creating Betty's Slack app}\n BETTY_BRAIN_URL: http://betty-brain:8000\n BETTY_BRAIN_TIMEOUT_SECONDS: ${BETTY_BRAIN_TIMEOUT_SECONDS:-60}\n BETTY_DAEMON_DATA_DIR: /data\n BETTY_DAEMON_THREAD_KEEP_MSGS: ${BETTY_DAEMON_THREAD_KEEP_MSGS:-20}\n volumes:\n - ./data/betty-daemon:/data\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n betty-operator:\n build:\n context: ./services/operator\n image: betty/operator-service:local\n container_name: betty-operator\n depends_on:\n postgres:\n condition: service_healthy\n betty-memory:\n condition: service_healthy\n betty-actions:\n condition: service_healthy\n betty-lookup:\n condition: service_healthy\n environment:\n BETTY_OPERATOR_POSTGRES_HOST: postgres\n BETTY_OPERATOR_POSTGRES_PORT: 5432\n BETTY_OPERATOR_POSTGRES_DB: ${POSTGRES_DB:-n8n}\n BETTY_OPERATOR_POSTGRES_USER: ${POSTGRES_USER:-n8n}\n BETTY_OPERATOR_POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n BETTY_OPERATOR_MEMORY_URL: http://betty-memory:8000\n BETTY_OPERATOR_ACTIONS_URL: http://betty-actions:8000\n BETTY_OPERATOR_LOOKUP_URL: http://betty-lookup:8000\n BETTY_OPERATOR_WRAPPER_URL: http://ollama-wrapper:8000/v1/chat/completions\n BETTY_OPERATOR_WRAPPER_USERNAME: ${WRAPPER_API_USERNAME:?set WRAPPER_API_USERNAME in .env}\n BETTY_OPERATOR_WRAPPER_PASSWORD: ${WRAPPER_API_PASSWORD:?set WRAPPER_API_PASSWORD in .env}\n BETTY_OPERATOR_WRAPPER_BEARER_TOKEN: ${WRAPPER_API_BEARER_TOKEN:-}\n BETTY_OPERATOR_WRAPPER_TIMEOUT_SECONDS: ${WRAPPER_REQUEST_TIMEOUT_SECONDS:-120}\n BETTY_OPERATOR_DEFAULT_TIMEZONE: ${GENERIC_TIMEZONE:-America/Los_Angeles}\n BETTY_PUBLIC_BASE_URL: ${BETTY_PUBLIC_BASE_URL:-https://YOUR_PUBLIC_URL}\n BETTY_OWNER_PHONE: ${BETTY_OWNER_PHONE:-}\n BETTY_OWNER_NAME: ${BETTY_OWNER_NAME:-Vincent}\n BETTY_HOME_CITY: ${BETTY_HOME_CITY:-Corona Del Mar, CA}\n BETTY_OPERATOR_STT_PROVIDER: ${BETTY_OPERATOR_STT_PROVIDER:-whisper_local}\n BETTY_OPERATOR_WHISPER_MODEL: ${BETTY_OPERATOR_WHISPER_MODEL:-base.en}\n BETTY_OPERATOR_WHISPER_DEVICE: ${BETTY_OPERATOR_WHISPER_DEVICE:-cpu}\n BETTY_OPERATOR_WHISPER_COMPUTE_TYPE: ${BETTY_OPERATOR_WHISPER_COMPUTE_TYPE:-int8}\n BETTY_OPERATOR_WHISPER_DATA_DIR: ${BETTY_OPERATOR_WHISPER_DATA_DIR:-/app/data/whisper}\n BETTY_MODEL_MODE: ${BETTY_MODEL_MODE:-default}\n BETTY_DEFAULT_CHAT_PROVIDER: ${BETTY_DEFAULT_CHAT_PROVIDER:-auto}\n BETTY_DEFAULT_CHAT_MODEL: ${BETTY_DEFAULT_CHAT_MODEL:-deepseek-chat}\n BETTY_DEFAULT_LOCAL_CHAT_MODEL: ${BETTY_DEFAULT_LOCAL_CHAT_MODEL:-llama3.1:8b}\n BETTY_MODEL_DOLPHIN: ${BETTY_MODEL_DOLPHIN:-dolphin-llama3:70b}\n BETTY_MODEL_MEDICAL: ${BETTY_MODEL_MEDICAL:-medllama2}\n BETTY_MODEL_MUSIC: ${BETTY_MODEL_MUSIC:-llamusic/llamusic}\n BETTY_MODEL_IMAGE: ${BETTY_MODEL_IMAGE:-llava:34b}\n BETTY_MODEL_WRITE: ${BETTY_MODEL_WRITE:-HammerAI/mythomax-l2}\n BETTY_MODEL_CODE: ${BETTY_MODEL_CODE:-qwen2.5-coder:32b}\n BETTY_MODEL_RESEARCH: ${BETTY_MODEL_RESEARCH:-nemotron}\n BETTY_MODEL_FAST: ${BETTY_MODEL_FAST:-gemma2:27b}\n DEEPSEEK_API_KEY: ${DEEPSEEK_API_KEY:-}\n DEEPSEEK_BASE_URL: ${DEEPSEEK_BASE_URL:-https://api.deepseek.com}\n BETTY_DEEPSEEK_MODEL: ${BETTY_DEEPSEEK_MODEL:-deepseek-chat}\n BETTY_DEEPSEEK_REASONER_MODEL: ${BETTY_DEEPSEEK_REASONER_MODEL:-deepseek-reasoner}\n BETTY_DEEPSEEK_TIMEOUT_SECONDS: ${BETTY_DEEPSEEK_TIMEOUT_SECONDS:-120}\n BETTY_DEEPSEEK_CHAT_INPUT_COST_PER_1M_USD: ${BETTY_DEEPSEEK_CHAT_INPUT_COST_PER_1M_USD:-0.27}\n BETTY_DEEPSEEK_CHAT_OUTPUT_COST_PER_1M_USD: ${BETTY_DEEPSEEK_CHAT_OUTPUT_COST_PER_1M_USD:-1.10}\n BETTY_DEEPSEEK_REASONER_INPUT_COST_PER_1M_USD: ${BETTY_DEEPSEEK_REASONER_INPUT_COST_PER_1M_USD:-0.55}\n BETTY_DEEPSEEK_REASONER_OUTPUT_COST_PER_1M_USD: ${BETTY_DEEPSEEK_REASONER_OUTPUT_COST_PER_1M_USD:-2.19}\n KOKORO_ENABLED: ${KOKORO_ENABLED:-true}\n KOKORO_DEFAULT_VOICE: ${KOKORO_DEFAULT_VOICE:-af_bella}\n BETTY_OPERATOR_KOKORO_SPEED: ${BETTY_OPERATOR_KOKORO_SPEED:-1.0}\n BETTY_OPERATOR_KOKORO_DATA_DIR: ${BETTY_OPERATOR_KOKORO_DATA_DIR:-/app/data/kokoro}\n ELEVENLABS_ENABLED: ${ELEVENLABS_ENABLED:-true}\n TWILIO_ENABLED: ${TWILIO_ENABLED:-true}\n VAPI_ENABLED: ${VAPI_ENABLED:-true}\n BETTY_COST_DAILY_CAP_USD: ${BETTY_COST_DAILY_CAP_USD:-5}\n BETTY_COST_MONTHLY_CAP_USD: ${BETTY_COST_MONTHLY_CAP_USD:-50}\n BETTY_COST_WARNING_RATIO: ${BETTY_COST_WARNING_RATIO:-0.8}\n BETTY_ELEVENLABS_COST_PER_1K_CHARS_USD: ${BETTY_ELEVENLABS_COST_PER_1K_CHARS_USD:-0.30}\n BETTY_TWILIO_SMS_COST_USD: ${BETTY_TWILIO_SMS_COST_USD:-0.008}\n BETTY_VAPI_COST_PER_MINUTE_USD: ${BETTY_VAPI_COST_PER_MINUTE_USD:-0.075}\n ELEVENLABS_API_KEY: ${ELEVENLABS_API_KEY:-}\n ELEVENLABS_VOICE_ID: ${ELEVENLABS_VOICE_ID:-4tRn1lSkEn13EVTuqb0g}\n ELEVENLABS_MODEL_ID: ${ELEVENLABS_MODEL_ID:-eleven_turbo_v2_5}\n PIPER_VOICE_MODEL: ${PIPER_VOICE_MODEL:-en_US-lessac-medium}\n BETTY_OPERATOR_PIPER_DATA_DIR: ${BETTY_OPERATOR_PIPER_DATA_DIR:-/app/data/piper}\n GOOGLE_CALENDAR_CLIENT_ID: ${GOOGLE_CALENDAR_CLIENT_ID:-}\n GOOGLE_CALENDAR_CLIENT_SECRET: ${GOOGLE_CALENDAR_CLIENT_SECRET:-}\n GOOGLE_CALENDAR_OAUTH_REDIRECT_URI: ${GOOGLE_CALENDAR_OAUTH_REDIRECT_URI:-}\n BETTY_OPERATOR_RATE_LIMIT_WINDOW_SECONDS: ${BETTY_OPERATOR_RATE_LIMIT_WINDOW_SECONDS:-60}\n BETTY_OPERATOR_ELEVENLABS_RATE_LIMIT: ${BETTY_OPERATOR_ELEVENLABS_RATE_LIMIT:-20}\n BETTY_OPERATOR_TWILIO_RATE_LIMIT: ${BETTY_OPERATOR_TWILIO_RATE_LIMIT:-20}\n VAPI_API_KEY: ${VAPI_API_KEY:-}\n VAPI_ASSISTANT_ID: ${VAPI_ASSISTANT_ID:-}\n VAPI_WEBHOOK_SECRET: ${VAPI_WEBHOOK_SECRET:-}\n TWILIO_ACCOUNT_SID: ${TWILIO_ACCOUNT_SID:-}\n TWILIO_AUTH_TOKEN: ${TWILIO_AUTH_TOKEN:-}\n TWILIO_PHONE_NUMBER: ${TWILIO_PHONE_NUMBER:-+19492391845}\n ports:\n - \"0.0.0.0:${BETTY_OPERATOR_HOST_PORT:-18120}:8000\"\n volumes:\n - betty_operator_data:/app/data\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 15s\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n n8n:\n image: n8nio/n8n:latest\n container_name: betty-n8n\n depends_on:\n postgres:\n condition: service_healthy\n ollama:\n condition: service_healthy\n ollama-wrapper:\n condition: service_healthy\n betty-memory:\n condition: service_healthy\n betty-actions:\n condition: service_healthy\n betty-lookup:\n condition: service_healthy\n betty-operator:\n condition: service_healthy\n environment:\n N8N_HOST: ${N8N_HOST:-localhost}\n N8N_PORT: 5678\n N8N_PROTOCOL: ${N8N_PROTOCOL:-http}\n WEBHOOK_URL: ${WEBHOOK_URL:-http://localhost:15678/}\n GENERIC_TIMEZONE: ${GENERIC_TIMEZONE:-America/Los_Angeles}\n N8N_ENCRYPTION_KEY: ${N8N_ENCRYPTION_KEY:?set N8N_ENCRYPTION_KEY in .env}\n N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS: \"true\"\n N8N_DIAGNOSTICS_ENABLED: \"false\"\n N8N_PERSONALIZATION_ENABLED: \"false\"\n N8N_SECURE_COOKIE: \"false\"\n N8N_BASIC_AUTH_ACTIVE: ${N8N_BASIC_AUTH_ACTIVE:-true}\n N8N_BASIC_AUTH_USER: ${N8N_BASIC_AUTH_USER:?set N8N_BASIC_AUTH_USER in .env}\n N8N_BASIC_AUTH_PASSWORD: ${N8N_BASIC_AUTH_PASSWORD:?set N8N_BASIC_AUTH_PASSWORD in .env}\n DB_TYPE: postgresdb\n DB_POSTGRESDB_HOST: postgres\n DB_POSTGRESDB_PORT: 5432\n DB_POSTGRESDB_DATABASE: ${POSTGRES_DB:-n8n}\n DB_POSTGRESDB_USER: ${POSTGRES_USER:-n8n}\n DB_POSTGRESDB_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n OLLAMA_WRAPPER_URL: http://ollama-wrapper:8000\n BETTY_MEMORY_URL: http://betty-memory:8000\n BETTY_ACTIONS_URL: http://betty-actions:8000\n BETTY_LOOKUP_URL: http://betty-lookup:8000\n BETTY_OPERATOR_URL: http://betty-operator:8000\n ports:\n - \"127.0.0.1:${N8N_HOST_PORT:-15678}:5678\"\n volumes:\n - n8n_data:/home/node/.n8n\n restart: unless-stopped\n security_opt:\n - no-new-privileges:true\n networks:\n - betty-net\n\nvolumes:\n postgres_data:\n chroma_data:\n ollama_data:\n betty_operator_data:\n n8n_data:\n\nnetworks:\n betty-net:\n driver: bridge\n"}, "5dc4686ab15e8a05dfc3b6a9a09b993864c5e590": {"t": 1785769594.987383, "n": 18658, "h": "7666f9328f5ae696", "c": "name: betty-stack\n\nservices:\n postgres:\n image: postgres:16-alpine\n container_name: betty-postgres\n environment:\n POSTGRES_USER: ${POSTGRES_USER:-n8n}\n POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n POSTGRES_DB: ${POSTGRES_DB:-n8n}\n volumes:\n - postgres_data:/var/lib/postgresql/data\n healthcheck:\n test: [\"CMD-SHELL\", \"pg_isready -U ${POSTGRES_USER:-n8n} -d ${POSTGRES_DB:-n8n}\"]\n interval: 10s\n timeout: 5s\n retries: 10\n start_period: 15s\n restart: unless-stopped\n security_opt:\n - no-new-privileges:true\n networks:\n - betty-net\n\n chroma:\n image: chromadb/chroma:0.5.23\n container_name: betty-chroma\n volumes:\n - chroma_data:/chroma/chroma\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/api/v1/heartbeat', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 15s\n restart: unless-stopped\n security_opt:\n - no-new-privileges:true\n networks:\n - betty-net\n\n ollama:\n image: ollama/ollama:latest\n container_name: betty-ollama\n ports:\n - \"127.0.0.1:${OLLAMA_HOST_PORT:-11435}:11434\"\n volumes:\n - ollama_data:/root/.ollama\n healthcheck:\n test: [\"CMD-SHELL\", \"ollama list >/dev/null 2>&1 || exit 1\"]\n interval: 20s\n timeout: 10s\n retries: 6\n start_period: 30s\n restart: unless-stopped\n security_opt:\n - no-new-privileges:true\n networks:\n - betty-net\n\n ollama-wrapper:\n build:\n context: ./ollama-wrapper\n image: betty/ollama-wrapper:local\n container_name: betty-ollama-wrapper\n depends_on:\n ollama:\n condition: service_healthy\n environment:\n OLLAMA_URL: http://ollama:11434\n API_USERNAME: ${WRAPPER_API_USERNAME:?set WRAPPER_API_USERNAME in .env}\n API_PASSWORD: ${WRAPPER_API_PASSWORD:?set WRAPPER_API_PASSWORD in .env}\n API_BEARER_TOKEN: ${WRAPPER_API_BEARER_TOKEN:-}\n WRAPPER_REQUEST_TIMEOUT_SECONDS: ${WRAPPER_REQUEST_TIMEOUT_SECONDS:-120}\n LOG_LEVEL: ${WRAPPER_LOG_LEVEL:-info}\n ports:\n - \"127.0.0.1:${WRAPPER_HOST_PORT:-18000}:8000\"\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 10s\n read_only: true\n tmpfs:\n - /tmp\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n betty-memory:\n build:\n context: ./services/memory\n image: betty/memory-service:local\n container_name: betty-memory\n depends_on:\n postgres:\n condition: service_healthy\n chroma:\n condition: service_healthy\n ollama:\n condition: service_healthy\n environment:\n BETTY_MEMORY_POSTGRES_HOST: postgres\n BETTY_MEMORY_POSTGRES_PORT: 5432\n BETTY_MEMORY_POSTGRES_DB: ${POSTGRES_DB:-n8n}\n BETTY_MEMORY_POSTGRES_USER: ${POSTGRES_USER:-n8n}\n BETTY_MEMORY_POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n BETTY_MEMORY_CHROMA_HOST: chroma\n BETTY_MEMORY_CHROMA_PORT: 8000\n BETTY_MEMORY_COLLECTION: ${BETTY_MEMORY_COLLECTION:-betty-v1-facts}\n BETTY_MEMORY_OLLAMA_URL: http://ollama:11434\n BETTY_MEMORY_EMBED_MODEL: ${BETTY_MEMORY_EMBED_MODEL:-nomic-embed-text}\n BETTY_MEMORY_SEMANTIC_DISTANCE_THRESHOLD: ${BETTY_MEMORY_SEMANTIC_DISTANCE_THRESHOLD:-0.45}\n BETTY_MEMORY_LOG_LEVEL: ${BETTY_MEMORY_LOG_LEVEL:-info}\n BETTY_MEMORY_DEFAULT_TIMEZONE: ${GENERIC_TIMEZONE:-America/Los_Angeles}\n BETTY_OWNER_NAME: ${BETTY_OWNER_NAME:-Vincent}\n BETTY_HOME_CITY: ${BETTY_HOME_CITY:-Corona Del Mar, CA}\n ports:\n - \"127.0.0.1:${BETTY_MEMORY_HOST_PORT:-18100}:8000\"\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 15s\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n betty-actions:\n build:\n context: ./services/actions\n image: betty/actions-service:local\n container_name: betty-actions\n depends_on:\n postgres:\n condition: service_healthy\n environment:\n BETTY_ACTIONS_POSTGRES_HOST: postgres\n BETTY_ACTIONS_POSTGRES_PORT: 5432\n BETTY_ACTIONS_POSTGRES_DB: ${POSTGRES_DB:-n8n}\n BETTY_ACTIONS_POSTGRES_USER: ${POSTGRES_USER:-n8n}\n BETTY_ACTIONS_POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n BETTY_ACTIONS_DEFAULT_TIMEZONE: ${GENERIC_TIMEZONE:-America/Los_Angeles}\n GOOGLE_CALENDAR_CLIENT_ID: ${GOOGLE_CALENDAR_CLIENT_ID:-}\n GOOGLE_CALENDAR_CLIENT_SECRET: ${GOOGLE_CALENDAR_CLIENT_SECRET:-}\n ports:\n - \"127.0.0.1:${BETTY_ACTIONS_HOST_PORT:-18110}:8000\"\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 15s\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n betty-lookup:\n build:\n context: ./services/lookup\n image: betty/lookup-service:local\n container_name: betty-lookup\n depends_on:\n postgres:\n condition: service_healthy\n ollama-wrapper:\n condition: service_healthy\n environment:\n BETTY_LOOKUP_POSTGRES_HOST: postgres\n BETTY_LOOKUP_POSTGRES_PORT: 5432\n BETTY_LOOKUP_POSTGRES_DB: ${POSTGRES_DB:-n8n}\n BETTY_LOOKUP_POSTGRES_USER: ${POSTGRES_USER:-n8n}\n BETTY_LOOKUP_POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n BETTY_LOOKUP_DEFAULT_TIMEZONE: ${GENERIC_TIMEZONE:-America/Los_Angeles}\n BETTY_OWNER_NAME: ${BETTY_OWNER_NAME:-Vincent}\n BETTY_HOME_CITY: ${BETTY_HOME_CITY:-Corona Del Mar, CA}\n BRAVE_ENABLED: ${BRAVE_ENABLED:-false}\n SEARXNG_ENABLED: ${SEARXNG_ENABLED:-true}\n SEARXNG_BASE_URL: ${SEARXNG_BASE_URL:-http://host.docker.internal:8080}\n BETTY_COST_DAILY_CAP_USD: ${BETTY_COST_DAILY_CAP_USD:-5}\n BETTY_COST_MONTHLY_CAP_USD: ${BETTY_COST_MONTHLY_CAP_USD:-50}\n BETTY_COST_WARNING_RATIO: ${BETTY_COST_WARNING_RATIO:-0.8}\n BRAVE_SEARCH_API_KEY: ${BRAVE_SEARCH_API_KEY:-}\n BETTY_LOOKUP_WRAPPER_URL: http://ollama-wrapper:8000/v1/chat/completions\n BETTY_LOOKUP_WRAPPER_USERNAME: ${WRAPPER_API_USERNAME:?set WRAPPER_API_USERNAME in .env}\n BETTY_LOOKUP_WRAPPER_PASSWORD: ${WRAPPER_API_PASSWORD:?set WRAPPER_API_PASSWORD in .env}\n BETTY_LOOKUP_WRAPPER_BEARER_TOKEN: ${WRAPPER_API_BEARER_TOKEN:-}\n BETTY_LOOKUP_MODEL: ${BETTY_LOOKUP_MODEL:-}\n BETTY_LOOKUP_WRAPPER_TIMEOUT_SECONDS: ${WRAPPER_REQUEST_TIMEOUT_SECONDS:-120}\n ports:\n - \"127.0.0.1:${BETTY_LOOKUP_HOST_PORT:-18130}:8000\"\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 15s\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n betty-brain:\n build:\n context: ./services/brain\n image: betty/brain-service:local\n container_name: betty-brain\n depends_on:\n postgres:\n condition: service_healthy\n betty-memory:\n condition: service_healthy\n betty-lookup:\n condition: service_healthy\n environment:\n BETTY_BRAIN_POSTGRES_HOST: postgres\n BETTY_BRAIN_POSTGRES_PORT: 5432\n BETTY_BRAIN_POSTGRES_DB: ${POSTGRES_DB:-n8n}\n BETTY_BRAIN_POSTGRES_USER: ${POSTGRES_USER:-n8n}\n BETTY_BRAIN_POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n BETTY_BRAIN_MODEL: ${BETTY_BRAIN_MODEL:-claude-haiku-4-5}\n BETTY_BRAIN_MAX_TOKENS: ${BETTY_BRAIN_MAX_TOKENS:-2000}\n BETTY_BRAIN_MAX_AGENT_ITERATIONS: ${BETTY_BRAIN_MAX_AGENT_ITERATIONS:-8}\n BETTY_BRAIN_MEMORY_URL: http://betty-memory:8000\n BETTY_BRAIN_LOOKUP_URL: http://betty-lookup:8000\n BETTY_BRAIN_TOOL_HTTP_TIMEOUT: ${BETTY_BRAIN_TOOL_HTTP_TIMEOUT:-30}\n BETTY_BRAIN_LOOKUP_HTTP_TIMEOUT: ${BETTY_BRAIN_LOOKUP_HTTP_TIMEOUT:-60}\n ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:?set ANTHROPIC_API_KEY in .env}\n BETTY_COST_DAILY_CAP_USD: ${BETTY_COST_DAILY_CAP_USD:-1}\n BETTY_COST_MONTHLY_CAP_USD: ${BETTY_COST_MONTHLY_CAP_USD:-30}\n BETTY_COST_WARNING_RATIO: ${BETTY_COST_WARNING_RATIO:-0.8}\n BETTY_OWNER_NAME: ${BETTY_OWNER_NAME:-Vincent}\n BETTY_HOME_CITY: ${BETTY_HOME_CITY:-Rancho Santa Margarita, CA}\n ports:\n - \"127.0.0.1:${BETTY_BRAIN_HOST_PORT:-18140}:8000\"\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 15s\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n betty-daemon:\n build:\n context: ./services/betty-slack-daemon\n image: betty/betty-slack-daemon:local\n container_name: betty-daemon\n depends_on:\n betty-brain:\n condition: service_healthy\n environment:\n BETTY_SLACK_APP_TOKEN: ${BETTY_SLACK_APP_TOKEN:?set BETTY_SLACK_APP_TOKEN in .env after creating Betty's Slack app}\n BETTY_SLACK_BOT_TOKEN: ${BETTY_SLACK_BOT_TOKEN:?set BETTY_SLACK_BOT_TOKEN in .env after creating Betty's Slack app}\n BETTY_BRAIN_URL: http://betty-brain:8000\n BETTY_BRAIN_TIMEOUT_SECONDS: ${BETTY_BRAIN_TIMEOUT_SECONDS:-60}\n BETTY_DAEMON_DATA_DIR: /data\n BETTY_DAEMON_THREAD_KEEP_MSGS: ${BETTY_DAEMON_THREAD_KEEP_MSGS:-20}\n volumes:\n - ./data/betty-daemon:/data\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n betty-operator:\n build:\n context: ./services/operator\n image: betty/operator-service:local\n container_name: betty-operator\n depends_on:\n postgres:\n condition: service_healthy\n betty-memory:\n condition: service_healthy\n betty-actions:\n condition: service_healthy\n betty-lookup:\n condition: service_healthy\n environment:\n BETTY_OPERATOR_POSTGRES_HOST: postgres\n BETTY_OPERATOR_POSTGRES_PORT: 5432\n BETTY_OPERATOR_POSTGRES_DB: ${POSTGRES_DB:-n8n}\n BETTY_OPERATOR_POSTGRES_USER: ${POSTGRES_USER:-n8n}\n BETTY_OPERATOR_POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n BETTY_OPERATOR_MEMORY_URL: http://betty-memory:8000\n BETTY_OPERATOR_ACTIONS_URL: http://betty-actions:8000\n BETTY_OPERATOR_LOOKUP_URL: http://betty-lookup:8000\n BETTY_OPERATOR_WRAPPER_URL: http://ollama-wrapper:8000/v1/chat/completions\n BETTY_OPERATOR_WRAPPER_USERNAME: ${WRAPPER_API_USERNAME:?set WRAPPER_API_USERNAME in .env}\n BETTY_OPERATOR_WRAPPER_PASSWORD: ${WRAPPER_API_PASSWORD:?set WRAPPER_API_PASSWORD in .env}\n BETTY_OPERATOR_WRAPPER_BEARER_TOKEN: ${WRAPPER_API_BEARER_TOKEN:-}\n BETTY_OPERATOR_WRAPPER_TIMEOUT_SECONDS: ${WRAPPER_REQUEST_TIMEOUT_SECONDS:-120}\n BETTY_OPERATOR_DEFAULT_TIMEZONE: ${GENERIC_TIMEZONE:-America/Los_Angeles}\n BETTY_PUBLIC_BASE_URL: ${BETTY_PUBLIC_BASE_URL:-https://YOUR_PUBLIC_URL}\n BETTY_OWNER_PHONE: ${BETTY_OWNER_PHONE:-}\n BETTY_OWNER_NAME: ${BETTY_OWNER_NAME:-Vincent}\n BETTY_HOME_CITY: ${BETTY_HOME_CITY:-Corona Del Mar, CA}\n BETTY_OPERATOR_STT_PROVIDER: ${BETTY_OPERATOR_STT_PROVIDER:-whisper_local}\n BETTY_OPERATOR_WHISPER_MODEL: ${BETTY_OPERATOR_WHISPER_MODEL:-base.en}\n BETTY_OPERATOR_WHISPER_DEVICE: ${BETTY_OPERATOR_WHISPER_DEVICE:-cpu}\n BETTY_OPERATOR_WHISPER_COMPUTE_TYPE: ${BETTY_OPERATOR_WHISPER_COMPUTE_TYPE:-int8}\n BETTY_OPERATOR_WHISPER_DATA_DIR: ${BETTY_OPERATOR_WHISPER_DATA_DIR:-/app/data/whisper}\n BETTY_MODEL_MODE: ${BETTY_MODEL_MODE:-default}\n BETTY_DEFAULT_CHAT_PROVIDER: ${BETTY_DEFAULT_CHAT_PROVIDER:-auto}\n BETTY_DEFAULT_CHAT_MODEL: ${BETTY_DEFAULT_CHAT_MODEL:-deepseek-chat}\n BETTY_DEFAULT_LOCAL_CHAT_MODEL: ${BETTY_DEFAULT_LOCAL_CHAT_MODEL:-llama3.1:8b}\n BETTY_MODEL_DOLPHIN: ${BETTY_MODEL_DOLPHIN:-dolphin-llama3:70b}\n BETTY_MODEL_MEDICAL: ${BETTY_MODEL_MEDICAL:-medllama2}\n BETTY_MODEL_MUSIC: ${BETTY_MODEL_MUSIC:-llamusic/llamusic}\n BETTY_MODEL_IMAGE: ${BETTY_MODEL_IMAGE:-llava:34b}\n BETTY_MODEL_WRITE: ${BETTY_MODEL_WRITE:-HammerAI/mythomax-l2}\n BETTY_MODEL_CODE: ${BETTY_MODEL_CODE:-qwen2.5-coder:32b}\n BETTY_MODEL_RESEARCH: ${BETTY_MODEL_RESEARCH:-nemotron}\n BETTY_MODEL_FAST: ${BETTY_MODEL_FAST:-gemma2:27b}\n DEEPSEEK_API_KEY: ${DEEPSEEK_API_KEY:-}\n DEEPSEEK_BASE_URL: ${DEEPSEEK_BASE_URL:-https://api.deepseek.com}\n BETTY_DEEPSEEK_MODEL: ${BETTY_DEEPSEEK_MODEL:-deepseek-chat}\n BETTY_DEEPSEEK_REASONER_MODEL: ${BETTY_DEEPSEEK_REASONER_MODEL:-deepseek-reasoner}\n BETTY_DEEPSEEK_TIMEOUT_SECONDS: ${BETTY_DEEPSEEK_TIMEOUT_SECONDS:-120}\n BETTY_DEEPSEEK_CHAT_INPUT_COST_PER_1M_USD: ${BETTY_DEEPSEEK_CHAT_INPUT_COST_PER_1M_USD:-0.27}\n BETTY_DEEPSEEK_CHAT_OUTPUT_COST_PER_1M_USD: ${BETTY_DEEPSEEK_CHAT_OUTPUT_COST_PER_1M_USD:-1.10}\n BETTY_DEEPSEEK_REASONER_INPUT_COST_PER_1M_USD: ${BETTY_DEEPSEEK_REASONER_INPUT_COST_PER_1M_USD:-0.55}\n BETTY_DEEPSEEK_REASONER_OUTPUT_COST_PER_1M_USD: ${BETTY_DEEPSEEK_REASONER_OUTPUT_COST_PER_1M_USD:-2.19}\n KOKORO_ENABLED: ${KOKORO_ENABLED:-true}\n KOKORO_DEFAULT_VOICE: ${KOKORO_DEFAULT_VOICE:-af_bella}\n BETTY_OPERATOR_KOKORO_SPEED: ${BETTY_OPERATOR_KOKORO_SPEED:-1.0}\n BETTY_OPERATOR_KOKORO_DATA_DIR: ${BETTY_OPERATOR_KOKORO_DATA_DIR:-/app/data/kokoro}\n ELEVENLABS_ENABLED: ${ELEVENLABS_ENABLED:-true}\n TWILIO_ENABLED: ${TWILIO_ENABLED:-true}\n VAPI_ENABLED: ${VAPI_ENABLED:-true}\n BETTY_COST_DAILY_CAP_USD: ${BETTY_COST_DAILY_CAP_USD:-5}\n BETTY_COST_MONTHLY_CAP_USD: ${BETTY_COST_MONTHLY_CAP_USD:-50}\n BETTY_COST_WARNING_RATIO: ${BETTY_COST_WARNING_RATIO:-0.8}\n BETTY_ELEVENLABS_COST_PER_1K_CHARS_USD: ${BETTY_ELEVENLABS_COST_PER_1K_CHARS_USD:-0.30}\n BETTY_TWILIO_SMS_COST_USD: ${BETTY_TWILIO_SMS_COST_USD:-0.008}\n BETTY_VAPI_COST_PER_MINUTE_USD: ${BETTY_VAPI_COST_PER_MINUTE_USD:-0.075}\n ELEVENLABS_API_KEY: ${ELEVENLABS_API_KEY:-}\n ELEVENLABS_VOICE_ID: ${ELEVENLABS_VOICE_ID:-4tRn1lSkEn13EVTuqb0g}\n ELEVENLABS_MODEL_ID: ${ELEVENLABS_MODEL_ID:-eleven_turbo_v2_5}\n PIPER_VOICE_MODEL: ${PIPER_VOICE_MODEL:-en_US-lessac-medium}\n BETTY_OPERATOR_PIPER_DATA_DIR: ${BETTY_OPERATOR_PIPER_DATA_DIR:-/app/data/piper}\n GOOGLE_CALENDAR_CLIENT_ID: ${GOOGLE_CALENDAR_CLIENT_ID:-}\n GOOGLE_CALENDAR_CLIENT_SECRET: ${GOOGLE_CALENDAR_CLIENT_SECRET:-}\n GOOGLE_CALENDAR_OAUTH_REDIRECT_URI: ${GOOGLE_CALENDAR_OAUTH_REDIRECT_URI:-}\n BETTY_OPERATOR_RATE_LIMIT_WINDOW_SECONDS: ${BETTY_OPERATOR_RATE_LIMIT_WINDOW_SECONDS:-60}\n BETTY_OPERATOR_ELEVENLABS_RATE_LIMIT: ${BETTY_OPERATOR_ELEVENLABS_RATE_LIMIT:-20}\n BETTY_OPERATOR_TWILIO_RATE_LIMIT: ${BETTY_OPERATOR_TWILIO_RATE_LIMIT:-20}\n VAPI_API_KEY: ${VAPI_API_KEY:-}\n VAPI_ASSISTANT_ID: ${VAPI_ASSISTANT_ID:-}\n VAPI_WEBHOOK_SECRET: ${VAPI_WEBHOOK_SECRET:-}\n TWILIO_ACCOUNT_SID: ${TWILIO_ACCOUNT_SID:-}\n TWILIO_AUTH_TOKEN: ${TWILIO_AUTH_TOKEN:-}\n TWILIO_PHONE_NUMBER: ${TWILIO_PHONE_NUMBER:-+19492391845}\n ports:\n - \"0.0.0.0:${BETTY_OPERATOR_HOST_PORT:-18120}:8000\"\n volumes:\n - betty_operator_data:/app/data\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 15s\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n n8n:\n image: n8nio/n8n:latest\n container_name: betty-n8n\n depends_on:\n postgres:\n condition: service_healthy\n ollama:\n condition: service_healthy\n ollama-wrapper:\n condition: service_healthy\n betty-memory:\n condition: service_healthy\n betty-actions:\n condition: service_healthy\n betty-lookup:\n condition: service_healthy\n betty-operator:\n condition: service_healthy\n environment:\n N8N_HOST: ${N8N_HOST:-localhost}\n N8N_PORT: 5678\n N8N_PROTOCOL: ${N8N_PROTOCOL:-http}\n WEBHOOK_URL: ${WEBHOOK_URL:-http://localhost:15678/}\n GENERIC_TIMEZONE: ${GENERIC_TIMEZONE:-America/Los_Angeles}\n N8N_ENCRYPTION_KEY: ${N8N_ENCRYPTION_KEY:?set N8N_ENCRYPTION_KEY in .env}\n N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS: \"true\"\n N8N_DIAGNOSTICS_ENABLED: \"false\"\n N8N_PERSONALIZATION_ENABLED: \"false\"\n N8N_SECURE_COOKIE: \"false\"\n N8N_BASIC_AUTH_ACTIVE: ${N8N_BASIC_AUTH_ACTIVE:-true}\n N8N_BASIC_AUTH_USER: ${N8N_BASIC_AUTH_USER:?set N8N_BASIC_AUTH_USER in .env}\n N8N_BASIC_AUTH_PASSWORD: ${N8N_BASIC_AUTH_PASSWORD:?set N8N_BASIC_AUTH_PASSWORD in .env}\n DB_TYPE: postgresdb\n DB_POSTGRESDB_HOST: postgres\n DB_POSTGRESDB_PORT: 5432\n DB_POSTGRESDB_DATABASE: ${POSTGRES_DB:-n8n}\n DB_POSTGRESDB_USER: ${POSTGRES_USER:-n8n}\n DB_POSTGRESDB_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n OLLAMA_WRAPPER_URL: http://ollama-wrapper:8000\n BETTY_MEMORY_URL: http://betty-memory:8000\n BETTY_ACTIONS_URL: http://betty-actions:8000\n BETTY_LOOKUP_URL: http://betty-lookup:8000\n BETTY_OPERATOR_URL: http://betty-operator:8000\n ports:\n - \"127.0.0.1:${N8N_HOST_PORT:-15678}:5678\"\n volumes:\n - n8n_data:/home/node/.n8n\n restart: unless-stopped\n security_opt:\n - no-new-privileges:true\n networks:\n - betty-net\n\nvolumes:\n postgres_data:\n chroma_data:\n ollama_data:\n betty_operator_data:\n n8n_data:\n\nnetworks:\n betty-net:\n driver: bridge\n"}, "9fc86cf915b71aa3c07a262f8dfa0e4191a22815": {"t": 1785769596.221914, "n": 18470, "h": "87890a1ebdaaa77b", "c": "name: betty-stack\n\nservices:\n postgres:\n image: postgres:16-alpine\n container_name: betty-postgres\n environment:\n POSTGRES_USER: ${POSTGRES_USER:-n8n}\n POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n POSTGRES_DB: ${POSTGRES_DB:-n8n}\n volumes:\n - postgres_data:/var/lib/postgresql/data\n healthcheck:\n test: [\"CMD-SHELL\", \"pg_isready -U ${POSTGRES_USER:-n8n} -d ${POSTGRES_DB:-n8n}\"]\n interval: 10s\n timeout: 5s\n retries: 10\n start_period: 15s\n restart: unless-stopped\n security_opt:\n - no-new-privileges:true\n networks:\n - betty-net\n\n chroma:\n image: chromadb/chroma:0.5.23\n container_name: betty-chroma\n volumes:\n - chroma_data:/chroma/chroma\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/api/v1/heartbeat', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 15s\n restart: unless-stopped\n security_opt:\n - no-new-privileges:true\n networks:\n - betty-net\n\n ollama:\n image: ollama/ollama:latest\n container_name: betty-ollama\n ports:\n - \"127.0.0.1:${OLLAMA_HOST_PORT:-11435}:11434\"\n volumes:\n - ollama_data:/root/.ollama\n healthcheck:\n test: [\"CMD-SHELL\", \"ollama list >/dev/null 2>&1 || exit 1\"]\n interval: 20s\n timeout: 10s\n retries: 6\n start_period: 30s\n restart: unless-stopped\n security_opt:\n - no-new-privileges:true\n networks:\n - betty-net\n\n ollama-wrapper:\n build:\n context: ./ollama-wrapper\n image: betty/ollama-wrapper:local\n container_name: betty-ollama-wrapper\n depends_on:\n ollama:\n condition: service_healthy\n environment:\n OLLAMA_URL: http://ollama:11434\n API_USERNAME: ${WRAPPER_API_USERNAME:?set WRAPPER_API_USERNAME in .env}\n API_PASSWORD: ${WRAPPER_API_PASSWORD:?set WRAPPER_API_PASSWORD in .env}\n API_BEARER_TOKEN: ${WRAPPER_API_BEARER_TOKEN:-}\n WRAPPER_REQUEST_TIMEOUT_SECONDS: ${WRAPPER_REQUEST_TIMEOUT_SECONDS:-120}\n LOG_LEVEL: ${WRAPPER_LOG_LEVEL:-info}\n ports:\n - \"127.0.0.1:${WRAPPER_HOST_PORT:-18000}:8000\"\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 10s\n read_only: true\n tmpfs:\n - /tmp\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n betty-memory:\n build:\n context: ./services/memory\n image: betty/memory-service:local\n container_name: betty-memory\n depends_on:\n postgres:\n condition: service_healthy\n chroma:\n condition: service_healthy\n ollama:\n condition: service_healthy\n environment:\n BETTY_MEMORY_POSTGRES_HOST: postgres\n BETTY_MEMORY_POSTGRES_PORT: 5432\n BETTY_MEMORY_POSTGRES_DB: ${POSTGRES_DB:-n8n}\n BETTY_MEMORY_POSTGRES_USER: ${POSTGRES_USER:-n8n}\n BETTY_MEMORY_POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n BETTY_MEMORY_CHROMA_HOST: chroma\n BETTY_MEMORY_CHROMA_PORT: 8000\n BETTY_MEMORY_COLLECTION: ${BETTY_MEMORY_COLLECTION:-betty-v1-facts}\n BETTY_MEMORY_OLLAMA_URL: http://ollama:11434\n BETTY_MEMORY_EMBED_MODEL: ${BETTY_MEMORY_EMBED_MODEL:-nomic-embed-text}\n BETTY_MEMORY_SEMANTIC_DISTANCE_THRESHOLD: ${BETTY_MEMORY_SEMANTIC_DISTANCE_THRESHOLD:-0.45}\n BETTY_MEMORY_LOG_LEVEL: ${BETTY_MEMORY_LOG_LEVEL:-info}\n BETTY_MEMORY_DEFAULT_TIMEZONE: ${GENERIC_TIMEZONE:-America/Los_Angeles}\n BETTY_OWNER_NAME: ${BETTY_OWNER_NAME:-Vincent}\n BETTY_HOME_CITY: ${BETTY_HOME_CITY:-Corona Del Mar, CA}\n ports:\n - \"127.0.0.1:${BETTY_MEMORY_HOST_PORT:-18100}:8000\"\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 15s\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n betty-actions:\n build:\n context: ./services/actions\n image: betty/actions-service:local\n container_name: betty-actions\n depends_on:\n postgres:\n condition: service_healthy\n environment:\n BETTY_ACTIONS_POSTGRES_HOST: postgres\n BETTY_ACTIONS_POSTGRES_PORT: 5432\n BETTY_ACTIONS_POSTGRES_DB: ${POSTGRES_DB:-n8n}\n BETTY_ACTIONS_POSTGRES_USER: ${POSTGRES_USER:-n8n}\n BETTY_ACTIONS_POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n BETTY_ACTIONS_DEFAULT_TIMEZONE: ${GENERIC_TIMEZONE:-America/Los_Angeles}\n GOOGLE_CALENDAR_CLIENT_ID: ${GOOGLE_CALENDAR_CLIENT_ID:-}\n GOOGLE_CALENDAR_CLIENT_SECRET: ${GOOGLE_CALENDAR_CLIENT_SECRET:-}\n ports:\n - \"127.0.0.1:${BETTY_ACTIONS_HOST_PORT:-18110}:8000\"\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 15s\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n betty-lookup:\n build:\n context: ./services/lookup\n image: betty/lookup-service:local\n container_name: betty-lookup\n depends_on:\n postgres:\n condition: service_healthy\n ollama-wrapper:\n condition: service_healthy\n environment:\n BETTY_LOOKUP_POSTGRES_HOST: postgres\n BETTY_LOOKUP_POSTGRES_PORT: 5432\n BETTY_LOOKUP_POSTGRES_DB: ${POSTGRES_DB:-n8n}\n BETTY_LOOKUP_POSTGRES_USER: ${POSTGRES_USER:-n8n}\n BETTY_LOOKUP_POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n BETTY_LOOKUP_DEFAULT_TIMEZONE: ${GENERIC_TIMEZONE:-America/Los_Angeles}\n BETTY_OWNER_NAME: ${BETTY_OWNER_NAME:-Vincent}\n BETTY_HOME_CITY: ${BETTY_HOME_CITY:-Corona Del Mar, CA}\n BRAVE_ENABLED: ${BRAVE_ENABLED:-false}\n SEARXNG_ENABLED: ${SEARXNG_ENABLED:-true}\n SEARXNG_BASE_URL: ${SEARXNG_BASE_URL:-http://host.docker.internal:8080}\n BETTY_COST_DAILY_CAP_USD: ${BETTY_COST_DAILY_CAP_USD:-5}\n BETTY_COST_MONTHLY_CAP_USD: ${BETTY_COST_MONTHLY_CAP_USD:-50}\n BETTY_COST_WARNING_RATIO: ${BETTY_COST_WARNING_RATIO:-0.8}\n BRAVE_SEARCH_API_KEY: ${BRAVE_SEARCH_API_KEY:-}\n BETTY_LOOKUP_WRAPPER_URL: http://ollama-wrapper:8000/v1/chat/completions\n BETTY_LOOKUP_WRAPPER_USERNAME: ${WRAPPER_API_USERNAME:?set WRAPPER_API_USERNAME in .env}\n BETTY_LOOKUP_WRAPPER_PASSWORD: ${WRAPPER_API_PASSWORD:?set WRAPPER_API_PASSWORD in .env}\n BETTY_LOOKUP_WRAPPER_BEARER_TOKEN: ${WRAPPER_API_BEARER_TOKEN:-}\n BETTY_LOOKUP_MODEL: ${BETTY_LOOKUP_MODEL:-}\n BETTY_LOOKUP_WRAPPER_TIMEOUT_SECONDS: ${WRAPPER_REQUEST_TIMEOUT_SECONDS:-120}\n ports:\n - \"127.0.0.1:${BETTY_LOOKUP_HOST_PORT:-18130}:8000\"\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 15s\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n betty-brain:\n build:\n context: ./services/brain\n image: betty/brain-service:local\n container_name: betty-brain\n depends_on:\n postgres:\n condition: service_healthy\n betty-memory:\n condition: service_healthy\n environment:\n BETTY_BRAIN_POSTGRES_HOST: postgres\n BETTY_BRAIN_POSTGRES_PORT: 5432\n BETTY_BRAIN_POSTGRES_DB: ${POSTGRES_DB:-n8n}\n BETTY_BRAIN_POSTGRES_USER: ${POSTGRES_USER:-n8n}\n BETTY_BRAIN_POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n BETTY_BRAIN_MODEL: ${BETTY_BRAIN_MODEL:-claude-haiku-4-5}\n BETTY_BRAIN_MAX_TOKENS: ${BETTY_BRAIN_MAX_TOKENS:-2000}\n BETTY_BRAIN_MAX_AGENT_ITERATIONS: ${BETTY_BRAIN_MAX_AGENT_ITERATIONS:-8}\n BETTY_BRAIN_MEMORY_URL: http://betty-memory:8000\n BETTY_BRAIN_TOOL_HTTP_TIMEOUT: ${BETTY_BRAIN_TOOL_HTTP_TIMEOUT:-30}\n ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:?set ANTHROPIC_API_KEY in .env}\n BETTY_COST_DAILY_CAP_USD: ${BETTY_COST_DAILY_CAP_USD:-1}\n BETTY_COST_MONTHLY_CAP_USD: ${BETTY_COST_MONTHLY_CAP_USD:-30}\n BETTY_COST_WARNING_RATIO: ${BETTY_COST_WARNING_RATIO:-0.8}\n BETTY_OWNER_NAME: ${BETTY_OWNER_NAME:-Vincent}\n BETTY_HOME_CITY: ${BETTY_HOME_CITY:-Rancho Santa Margarita, CA}\n ports:\n - \"127.0.0.1:${BETTY_BRAIN_HOST_PORT:-18140}:8000\"\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 15s\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n betty-daemon:\n build:\n context: ./services/betty-slack-daemon\n image: betty/betty-slack-daemon:local\n container_name: betty-daemon\n depends_on:\n betty-brain:\n condition: service_healthy\n environment:\n BETTY_SLACK_APP_TOKEN: ${BETTY_SLACK_APP_TOKEN:?set BETTY_SLACK_APP_TOKEN in .env after creating Betty's Slack app}\n BETTY_SLACK_BOT_TOKEN: ${BETTY_SLACK_BOT_TOKEN:?set BETTY_SLACK_BOT_TOKEN in .env after creating Betty's Slack app}\n BETTY_BRAIN_URL: http://betty-brain:8000\n BETTY_BRAIN_TIMEOUT_SECONDS: ${BETTY_BRAIN_TIMEOUT_SECONDS:-60}\n BETTY_DAEMON_DATA_DIR: /data\n BETTY_DAEMON_THREAD_KEEP_MSGS: ${BETTY_DAEMON_THREAD_KEEP_MSGS:-20}\n volumes:\n - ./data/betty-daemon:/data\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n betty-operator:\n build:\n context: ./services/operator\n image: betty/operator-service:local\n container_name: betty-operator\n depends_on:\n postgres:\n condition: service_healthy\n betty-memory:\n condition: service_healthy\n betty-actions:\n condition: service_healthy\n betty-lookup:\n condition: service_healthy\n environment:\n BETTY_OPERATOR_POSTGRES_HOST: postgres\n BETTY_OPERATOR_POSTGRES_PORT: 5432\n BETTY_OPERATOR_POSTGRES_DB: ${POSTGRES_DB:-n8n}\n BETTY_OPERATOR_POSTGRES_USER: ${POSTGRES_USER:-n8n}\n BETTY_OPERATOR_POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n BETTY_OPERATOR_MEMORY_URL: http://betty-memory:8000\n BETTY_OPERATOR_ACTIONS_URL: http://betty-actions:8000\n BETTY_OPERATOR_LOOKUP_URL: http://betty-lookup:8000\n BETTY_OPERATOR_WRAPPER_URL: http://ollama-wrapper:8000/v1/chat/completions\n BETTY_OPERATOR_WRAPPER_USERNAME: ${WRAPPER_API_USERNAME:?set WRAPPER_API_USERNAME in .env}\n BETTY_OPERATOR_WRAPPER_PASSWORD: ${WRAPPER_API_PASSWORD:?set WRAPPER_API_PASSWORD in .env}\n BETTY_OPERATOR_WRAPPER_BEARER_TOKEN: ${WRAPPER_API_BEARER_TOKEN:-}\n BETTY_OPERATOR_WRAPPER_TIMEOUT_SECONDS: ${WRAPPER_REQUEST_TIMEOUT_SECONDS:-120}\n BETTY_OPERATOR_DEFAULT_TIMEZONE: ${GENERIC_TIMEZONE:-America/Los_Angeles}\n BETTY_PUBLIC_BASE_URL: ${BETTY_PUBLIC_BASE_URL:-https://YOUR_PUBLIC_URL}\n BETTY_OWNER_PHONE: ${BETTY_OWNER_PHONE:-}\n BETTY_OWNER_NAME: ${BETTY_OWNER_NAME:-Vincent}\n BETTY_HOME_CITY: ${BETTY_HOME_CITY:-Corona Del Mar, CA}\n BETTY_OPERATOR_STT_PROVIDER: ${BETTY_OPERATOR_STT_PROVIDER:-whisper_local}\n BETTY_OPERATOR_WHISPER_MODEL: ${BETTY_OPERATOR_WHISPER_MODEL:-base.en}\n BETTY_OPERATOR_WHISPER_DEVICE: ${BETTY_OPERATOR_WHISPER_DEVICE:-cpu}\n BETTY_OPERATOR_WHISPER_COMPUTE_TYPE: ${BETTY_OPERATOR_WHISPER_COMPUTE_TYPE:-int8}\n BETTY_OPERATOR_WHISPER_DATA_DIR: ${BETTY_OPERATOR_WHISPER_DATA_DIR:-/app/data/whisper}\n BETTY_MODEL_MODE: ${BETTY_MODEL_MODE:-default}\n BETTY_DEFAULT_CHAT_PROVIDER: ${BETTY_DEFAULT_CHAT_PROVIDER:-auto}\n BETTY_DEFAULT_CHAT_MODEL: ${BETTY_DEFAULT_CHAT_MODEL:-deepseek-chat}\n BETTY_DEFAULT_LOCAL_CHAT_MODEL: ${BETTY_DEFAULT_LOCAL_CHAT_MODEL:-llama3.1:8b}\n BETTY_MODEL_DOLPHIN: ${BETTY_MODEL_DOLPHIN:-dolphin-llama3:70b}\n BETTY_MODEL_MEDICAL: ${BETTY_MODEL_MEDICAL:-medllama2}\n BETTY_MODEL_MUSIC: ${BETTY_MODEL_MUSIC:-llamusic/llamusic}\n BETTY_MODEL_IMAGE: ${BETTY_MODEL_IMAGE:-llava:34b}\n BETTY_MODEL_WRITE: ${BETTY_MODEL_WRITE:-HammerAI/mythomax-l2}\n BETTY_MODEL_CODE: ${BETTY_MODEL_CODE:-qwen2.5-coder:32b}\n BETTY_MODEL_RESEARCH: ${BETTY_MODEL_RESEARCH:-nemotron}\n BETTY_MODEL_FAST: ${BETTY_MODEL_FAST:-gemma2:27b}\n DEEPSEEK_API_KEY: ${DEEPSEEK_API_KEY:-}\n DEEPSEEK_BASE_URL: ${DEEPSEEK_BASE_URL:-https://api.deepseek.com}\n BETTY_DEEPSEEK_MODEL: ${BETTY_DEEPSEEK_MODEL:-deepseek-chat}\n BETTY_DEEPSEEK_REASONER_MODEL: ${BETTY_DEEPSEEK_REASONER_MODEL:-deepseek-reasoner}\n BETTY_DEEPSEEK_TIMEOUT_SECONDS: ${BETTY_DEEPSEEK_TIMEOUT_SECONDS:-120}\n BETTY_DEEPSEEK_CHAT_INPUT_COST_PER_1M_USD: ${BETTY_DEEPSEEK_CHAT_INPUT_COST_PER_1M_USD:-0.27}\n BETTY_DEEPSEEK_CHAT_OUTPUT_COST_PER_1M_USD: ${BETTY_DEEPSEEK_CHAT_OUTPUT_COST_PER_1M_USD:-1.10}\n BETTY_DEEPSEEK_REASONER_INPUT_COST_PER_1M_USD: ${BETTY_DEEPSEEK_REASONER_INPUT_COST_PER_1M_USD:-0.55}\n BETTY_DEEPSEEK_REASONER_OUTPUT_COST_PER_1M_USD: ${BETTY_DEEPSEEK_REASONER_OUTPUT_COST_PER_1M_USD:-2.19}\n KOKORO_ENABLED: ${KOKORO_ENABLED:-true}\n KOKORO_DEFAULT_VOICE: ${KOKORO_DEFAULT_VOICE:-af_bella}\n BETTY_OPERATOR_KOKORO_SPEED: ${BETTY_OPERATOR_KOKORO_SPEED:-1.0}\n BETTY_OPERATOR_KOKORO_DATA_DIR: ${BETTY_OPERATOR_KOKORO_DATA_DIR:-/app/data/kokoro}\n ELEVENLABS_ENABLED: ${ELEVENLABS_ENABLED:-true}\n TWILIO_ENABLED: ${TWILIO_ENABLED:-true}\n VAPI_ENABLED: ${VAPI_ENABLED:-true}\n BETTY_COST_DAILY_CAP_USD: ${BETTY_COST_DAILY_CAP_USD:-5}\n BETTY_COST_MONTHLY_CAP_USD: ${BETTY_COST_MONTHLY_CAP_USD:-50}\n BETTY_COST_WARNING_RATIO: ${BETTY_COST_WARNING_RATIO:-0.8}\n BETTY_ELEVENLABS_COST_PER_1K_CHARS_USD: ${BETTY_ELEVENLABS_COST_PER_1K_CHARS_USD:-0.30}\n BETTY_TWILIO_SMS_COST_USD: ${BETTY_TWILIO_SMS_COST_USD:-0.008}\n BETTY_VAPI_COST_PER_MINUTE_USD: ${BETTY_VAPI_COST_PER_MINUTE_USD:-0.075}\n ELEVENLABS_API_KEY: ${ELEVENLABS_API_KEY:-}\n ELEVENLABS_VOICE_ID: ${ELEVENLABS_VOICE_ID:-4tRn1lSkEn13EVTuqb0g}\n ELEVENLABS_MODEL_ID: ${ELEVENLABS_MODEL_ID:-eleven_turbo_v2_5}\n PIPER_VOICE_MODEL: ${PIPER_VOICE_MODEL:-en_US-lessac-medium}\n BETTY_OPERATOR_PIPER_DATA_DIR: ${BETTY_OPERATOR_PIPER_DATA_DIR:-/app/data/piper}\n GOOGLE_CALENDAR_CLIENT_ID: ${GOOGLE_CALENDAR_CLIENT_ID:-}\n GOOGLE_CALENDAR_CLIENT_SECRET: ${GOOGLE_CALENDAR_CLIENT_SECRET:-}\n GOOGLE_CALENDAR_OAUTH_REDIRECT_URI: ${GOOGLE_CALENDAR_OAUTH_REDIRECT_URI:-}\n BETTY_OPERATOR_RATE_LIMIT_WINDOW_SECONDS: ${BETTY_OPERATOR_RATE_LIMIT_WINDOW_SECONDS:-60}\n BETTY_OPERATOR_ELEVENLABS_RATE_LIMIT: ${BETTY_OPERATOR_ELEVENLABS_RATE_LIMIT:-20}\n BETTY_OPERATOR_TWILIO_RATE_LIMIT: ${BETTY_OPERATOR_TWILIO_RATE_LIMIT:-20}\n VAPI_API_KEY: ${VAPI_API_KEY:-}\n VAPI_ASSISTANT_ID: ${VAPI_ASSISTANT_ID:-}\n VAPI_WEBHOOK_SECRET: ${VAPI_WEBHOOK_SECRET:-}\n TWILIO_ACCOUNT_SID: ${TWILIO_ACCOUNT_SID:-}\n TWILIO_AUTH_TOKEN: ${TWILIO_AUTH_TOKEN:-}\n TWILIO_PHONE_NUMBER: ${TWILIO_PHONE_NUMBER:-+19492391845}\n ports:\n - \"0.0.0.0:${BETTY_OPERATOR_HOST_PORT:-18120}:8000\"\n volumes:\n - betty_operator_data:/app/data\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 15s\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n n8n:\n image: n8nio/n8n:latest\n container_name: betty-n8n\n depends_on:\n postgres:\n condition: service_healthy\n ollama:\n condition: service_healthy\n ollama-wrapper:\n condition: service_healthy\n betty-memory:\n condition: service_healthy\n betty-actions:\n condition: service_healthy\n betty-lookup:\n condition: service_healthy\n betty-operator:\n condition: service_healthy\n environment:\n N8N_HOST: ${N8N_HOST:-localhost}\n N8N_PORT: 5678\n N8N_PROTOCOL: ${N8N_PROTOCOL:-http}\n WEBHOOK_URL: ${WEBHOOK_URL:-http://localhost:15678/}\n GENERIC_TIMEZONE: ${GENERIC_TIMEZONE:-America/Los_Angeles}\n N8N_ENCRYPTION_KEY: ${N8N_ENCRYPTION_KEY:?set N8N_ENCRYPTION_KEY in .env}\n N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS: \"true\"\n N8N_DIAGNOSTICS_ENABLED: \"false\"\n N8N_PERSONALIZATION_ENABLED: \"false\"\n N8N_SECURE_COOKIE: \"false\"\n N8N_BASIC_AUTH_ACTIVE: ${N8N_BASIC_AUTH_ACTIVE:-true}\n N8N_BASIC_AUTH_USER: ${N8N_BASIC_AUTH_USER:?set N8N_BASIC_AUTH_USER in .env}\n N8N_BASIC_AUTH_PASSWORD: ${N8N_BASIC_AUTH_PASSWORD:?set N8N_BASIC_AUTH_PASSWORD in .env}\n DB_TYPE: postgresdb\n DB_POSTGRESDB_HOST: postgres\n DB_POSTGRESDB_PORT: 5432\n DB_POSTGRESDB_DATABASE: ${POSTGRES_DB:-n8n}\n DB_POSTGRESDB_USER: ${POSTGRES_USER:-n8n}\n DB_POSTGRESDB_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n OLLAMA_WRAPPER_URL: http://ollama-wrapper:8000\n BETTY_MEMORY_URL: http://betty-memory:8000\n BETTY_ACTIONS_URL: http://betty-actions:8000\n BETTY_LOOKUP_URL: http://betty-lookup:8000\n BETTY_OPERATOR_URL: http://betty-operator:8000\n ports:\n - \"127.0.0.1:${N8N_HOST_PORT:-15678}:5678\"\n volumes:\n - n8n_data:/home/node/.n8n\n restart: unless-stopped\n security_opt:\n - no-new-privileges:true\n networks:\n - betty-net\n\nvolumes:\n postgres_data:\n chroma_data:\n ollama_data:\n betty_operator_data:\n n8n_data:\n\nnetworks:\n betty-net:\n driver: bridge\n"}, "19b7bec86d7fcb25bb64ef1ca60a903e1ff243b9": {"t": 1785769597.5997763, "n": 18207, "h": "05374b7722d80bcb", "c": "name: betty-stack\n\nservices:\n postgres:\n image: postgres:16-alpine\n container_name: betty-postgres\n environment:\n POSTGRES_USER: ${POSTGRES_USER:-n8n}\n POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n POSTGRES_DB: ${POSTGRES_DB:-n8n}\n volumes:\n - postgres_data:/var/lib/postgresql/data\n healthcheck:\n test: [\"CMD-SHELL\", \"pg_isready -U ${POSTGRES_USER:-n8n} -d ${POSTGRES_DB:-n8n}\"]\n interval: 10s\n timeout: 5s\n retries: 10\n start_period: 15s\n restart: unless-stopped\n security_opt:\n - no-new-privileges:true\n networks:\n - betty-net\n\n chroma:\n image: chromadb/chroma:0.5.23\n container_name: betty-chroma\n volumes:\n - chroma_data:/chroma/chroma\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/api/v1/heartbeat', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 15s\n restart: unless-stopped\n security_opt:\n - no-new-privileges:true\n networks:\n - betty-net\n\n ollama:\n image: ollama/ollama:latest\n container_name: betty-ollama\n ports:\n - \"127.0.0.1:${OLLAMA_HOST_PORT:-11435}:11434\"\n volumes:\n - ollama_data:/root/.ollama\n healthcheck:\n test: [\"CMD-SHELL\", \"ollama list >/dev/null 2>&1 || exit 1\"]\n interval: 20s\n timeout: 10s\n retries: 6\n start_period: 30s\n restart: unless-stopped\n security_opt:\n - no-new-privileges:true\n networks:\n - betty-net\n\n ollama-wrapper:\n build:\n context: ./ollama-wrapper\n image: betty/ollama-wrapper:local\n container_name: betty-ollama-wrapper\n depends_on:\n ollama:\n condition: service_healthy\n environment:\n OLLAMA_URL: http://ollama:11434\n API_USERNAME: ${WRAPPER_API_USERNAME:?set WRAPPER_API_USERNAME in .env}\n API_PASSWORD: ${WRAPPER_API_PASSWORD:?set WRAPPER_API_PASSWORD in .env}\n API_BEARER_TOKEN: ${WRAPPER_API_BEARER_TOKEN:-}\n WRAPPER_REQUEST_TIMEOUT_SECONDS: ${WRAPPER_REQUEST_TIMEOUT_SECONDS:-120}\n LOG_LEVEL: ${WRAPPER_LOG_LEVEL:-info}\n ports:\n - \"127.0.0.1:${WRAPPER_HOST_PORT:-18000}:8000\"\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 10s\n read_only: true\n tmpfs:\n - /tmp\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n betty-memory:\n build:\n context: ./services/memory\n image: betty/memory-service:local\n container_name: betty-memory\n depends_on:\n postgres:\n condition: service_healthy\n chroma:\n condition: service_healthy\n ollama:\n condition: service_healthy\n environment:\n BETTY_MEMORY_POSTGRES_HOST: postgres\n BETTY_MEMORY_POSTGRES_PORT: 5432\n BETTY_MEMORY_POSTGRES_DB: ${POSTGRES_DB:-n8n}\n BETTY_MEMORY_POSTGRES_USER: ${POSTGRES_USER:-n8n}\n BETTY_MEMORY_POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n BETTY_MEMORY_CHROMA_HOST: chroma\n BETTY_MEMORY_CHROMA_PORT: 8000\n BETTY_MEMORY_COLLECTION: ${BETTY_MEMORY_COLLECTION:-betty-v1-facts}\n BETTY_MEMORY_OLLAMA_URL: http://ollama:11434\n BETTY_MEMORY_EMBED_MODEL: ${BETTY_MEMORY_EMBED_MODEL:-nomic-embed-text}\n BETTY_MEMORY_SEMANTIC_DISTANCE_THRESHOLD: ${BETTY_MEMORY_SEMANTIC_DISTANCE_THRESHOLD:-0.45}\n BETTY_MEMORY_LOG_LEVEL: ${BETTY_MEMORY_LOG_LEVEL:-info}\n BETTY_MEMORY_DEFAULT_TIMEZONE: ${GENERIC_TIMEZONE:-America/Los_Angeles}\n BETTY_OWNER_NAME: ${BETTY_OWNER_NAME:-Vincent}\n BETTY_HOME_CITY: ${BETTY_HOME_CITY:-Corona Del Mar, CA}\n ports:\n - \"127.0.0.1:${BETTY_MEMORY_HOST_PORT:-18100}:8000\"\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 15s\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n betty-actions:\n build:\n context: ./services/actions\n image: betty/actions-service:local\n container_name: betty-actions\n depends_on:\n postgres:\n condition: service_healthy\n environment:\n BETTY_ACTIONS_POSTGRES_HOST: postgres\n BETTY_ACTIONS_POSTGRES_PORT: 5432\n BETTY_ACTIONS_POSTGRES_DB: ${POSTGRES_DB:-n8n}\n BETTY_ACTIONS_POSTGRES_USER: ${POSTGRES_USER:-n8n}\n BETTY_ACTIONS_POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n BETTY_ACTIONS_DEFAULT_TIMEZONE: ${GENERIC_TIMEZONE:-America/Los_Angeles}\n GOOGLE_CALENDAR_CLIENT_ID: ${GOOGLE_CALENDAR_CLIENT_ID:-}\n GOOGLE_CALENDAR_CLIENT_SECRET: ${GOOGLE_CALENDAR_CLIENT_SECRET:-}\n ports:\n - \"127.0.0.1:${BETTY_ACTIONS_HOST_PORT:-18110}:8000\"\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 15s\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n betty-lookup:\n build:\n context: ./services/lookup\n image: betty/lookup-service:local\n container_name: betty-lookup\n depends_on:\n postgres:\n condition: service_healthy\n ollama-wrapper:\n condition: service_healthy\n environment:\n BETTY_LOOKUP_POSTGRES_HOST: postgres\n BETTY_LOOKUP_POSTGRES_PORT: 5432\n BETTY_LOOKUP_POSTGRES_DB: ${POSTGRES_DB:-n8n}\n BETTY_LOOKUP_POSTGRES_USER: ${POSTGRES_USER:-n8n}\n BETTY_LOOKUP_POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n BETTY_LOOKUP_DEFAULT_TIMEZONE: ${GENERIC_TIMEZONE:-America/Los_Angeles}\n BETTY_OWNER_NAME: ${BETTY_OWNER_NAME:-Vincent}\n BETTY_HOME_CITY: ${BETTY_HOME_CITY:-Corona Del Mar, CA}\n BRAVE_ENABLED: ${BRAVE_ENABLED:-false}\n SEARXNG_ENABLED: ${SEARXNG_ENABLED:-true}\n SEARXNG_BASE_URL: ${SEARXNG_BASE_URL:-http://host.docker.internal:8080}\n BETTY_COST_DAILY_CAP_USD: ${BETTY_COST_DAILY_CAP_USD:-5}\n BETTY_COST_MONTHLY_CAP_USD: ${BETTY_COST_MONTHLY_CAP_USD:-50}\n BETTY_COST_WARNING_RATIO: ${BETTY_COST_WARNING_RATIO:-0.8}\n BRAVE_SEARCH_API_KEY: ${BRAVE_SEARCH_API_KEY:-}\n BETTY_LOOKUP_WRAPPER_URL: http://ollama-wrapper:8000/v1/chat/completions\n BETTY_LOOKUP_WRAPPER_USERNAME: ${WRAPPER_API_USERNAME:?set WRAPPER_API_USERNAME in .env}\n BETTY_LOOKUP_WRAPPER_PASSWORD: ${WRAPPER_API_PASSWORD:?set WRAPPER_API_PASSWORD in .env}\n BETTY_LOOKUP_WRAPPER_BEARER_TOKEN: ${WRAPPER_API_BEARER_TOKEN:-}\n BETTY_LOOKUP_MODEL: ${BETTY_LOOKUP_MODEL:-}\n BETTY_LOOKUP_WRAPPER_TIMEOUT_SECONDS: ${WRAPPER_REQUEST_TIMEOUT_SECONDS:-120}\n ports:\n - \"127.0.0.1:${BETTY_LOOKUP_HOST_PORT:-18130}:8000\"\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 15s\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n betty-brain:\n build:\n context: ./services/brain\n image: betty/brain-service:local\n container_name: betty-brain\n depends_on:\n postgres:\n condition: service_healthy\n environment:\n BETTY_BRAIN_POSTGRES_HOST: postgres\n BETTY_BRAIN_POSTGRES_PORT: 5432\n BETTY_BRAIN_POSTGRES_DB: ${POSTGRES_DB:-n8n}\n BETTY_BRAIN_POSTGRES_USER: ${POSTGRES_USER:-n8n}\n BETTY_BRAIN_POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n BETTY_BRAIN_MODEL: ${BETTY_BRAIN_MODEL:-claude-haiku-4-5}\n BETTY_BRAIN_MAX_TOKENS: ${BETTY_BRAIN_MAX_TOKENS:-2000}\n ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:?set ANTHROPIC_API_KEY in .env}\n BETTY_COST_DAILY_CAP_USD: ${BETTY_COST_DAILY_CAP_USD:-1}\n BETTY_COST_MONTHLY_CAP_USD: ${BETTY_COST_MONTHLY_CAP_USD:-30}\n BETTY_COST_WARNING_RATIO: ${BETTY_COST_WARNING_RATIO:-0.8}\n BETTY_OWNER_NAME: ${BETTY_OWNER_NAME:-Vincent}\n BETTY_HOME_CITY: ${BETTY_HOME_CITY:-Rancho Santa Margarita, CA}\n ports:\n - \"127.0.0.1:${BETTY_BRAIN_HOST_PORT:-18140}:8000\"\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 15s\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n betty-daemon:\n build:\n context: ./services/betty-slack-daemon\n image: betty/betty-slack-daemon:local\n container_name: betty-daemon\n depends_on:\n betty-brain:\n condition: service_healthy\n environment:\n BETTY_SLACK_APP_TOKEN: ${BETTY_SLACK_APP_TOKEN:?set BETTY_SLACK_APP_TOKEN in .env after creating Betty's Slack app}\n BETTY_SLACK_BOT_TOKEN: ${BETTY_SLACK_BOT_TOKEN:?set BETTY_SLACK_BOT_TOKEN in .env after creating Betty's Slack app}\n BETTY_BRAIN_URL: http://betty-brain:8000\n BETTY_BRAIN_TIMEOUT_SECONDS: ${BETTY_BRAIN_TIMEOUT_SECONDS:-60}\n BETTY_DAEMON_DATA_DIR: /data\n BETTY_DAEMON_THREAD_KEEP_MSGS: ${BETTY_DAEMON_THREAD_KEEP_MSGS:-20}\n volumes:\n - ./data/betty-daemon:/data\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n betty-operator:\n build:\n context: ./services/operator\n image: betty/operator-service:local\n container_name: betty-operator\n depends_on:\n postgres:\n condition: service_healthy\n betty-memory:\n condition: service_healthy\n betty-actions:\n condition: service_healthy\n betty-lookup:\n condition: service_healthy\n environment:\n BETTY_OPERATOR_POSTGRES_HOST: postgres\n BETTY_OPERATOR_POSTGRES_PORT: 5432\n BETTY_OPERATOR_POSTGRES_DB: ${POSTGRES_DB:-n8n}\n BETTY_OPERATOR_POSTGRES_USER: ${POSTGRES_USER:-n8n}\n BETTY_OPERATOR_POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n BETTY_OPERATOR_MEMORY_URL: http://betty-memory:8000\n BETTY_OPERATOR_ACTIONS_URL: http://betty-actions:8000\n BETTY_OPERATOR_LOOKUP_URL: http://betty-lookup:8000\n BETTY_OPERATOR_WRAPPER_URL: http://ollama-wrapper:8000/v1/chat/completions\n BETTY_OPERATOR_WRAPPER_USERNAME: ${WRAPPER_API_USERNAME:?set WRAPPER_API_USERNAME in .env}\n BETTY_OPERATOR_WRAPPER_PASSWORD: ${WRAPPER_API_PASSWORD:?set WRAPPER_API_PASSWORD in .env}\n BETTY_OPERATOR_WRAPPER_BEARER_TOKEN: ${WRAPPER_API_BEARER_TOKEN:-}\n BETTY_OPERATOR_WRAPPER_TIMEOUT_SECONDS: ${WRAPPER_REQUEST_TIMEOUT_SECONDS:-120}\n BETTY_OPERATOR_DEFAULT_TIMEZONE: ${GENERIC_TIMEZONE:-America/Los_Angeles}\n BETTY_PUBLIC_BASE_URL: ${BETTY_PUBLIC_BASE_URL:-https://YOUR_PUBLIC_URL}\n BETTY_OWNER_PHONE: ${BETTY_OWNER_PHONE:-}\n BETTY_OWNER_NAME: ${BETTY_OWNER_NAME:-Vincent}\n BETTY_HOME_CITY: ${BETTY_HOME_CITY:-Corona Del Mar, CA}\n BETTY_OPERATOR_STT_PROVIDER: ${BETTY_OPERATOR_STT_PROVIDER:-whisper_local}\n BETTY_OPERATOR_WHISPER_MODEL: ${BETTY_OPERATOR_WHISPER_MODEL:-base.en}\n BETTY_OPERATOR_WHISPER_DEVICE: ${BETTY_OPERATOR_WHISPER_DEVICE:-cpu}\n BETTY_OPERATOR_WHISPER_COMPUTE_TYPE: ${BETTY_OPERATOR_WHISPER_COMPUTE_TYPE:-int8}\n BETTY_OPERATOR_WHISPER_DATA_DIR: ${BETTY_OPERATOR_WHISPER_DATA_DIR:-/app/data/whisper}\n BETTY_MODEL_MODE: ${BETTY_MODEL_MODE:-default}\n BETTY_DEFAULT_CHAT_PROVIDER: ${BETTY_DEFAULT_CHAT_PROVIDER:-auto}\n BETTY_DEFAULT_CHAT_MODEL: ${BETTY_DEFAULT_CHAT_MODEL:-deepseek-chat}\n BETTY_DEFAULT_LOCAL_CHAT_MODEL: ${BETTY_DEFAULT_LOCAL_CHAT_MODEL:-llama3.1:8b}\n BETTY_MODEL_DOLPHIN: ${BETTY_MODEL_DOLPHIN:-dolphin-llama3:70b}\n BETTY_MODEL_MEDICAL: ${BETTY_MODEL_MEDICAL:-medllama2}\n BETTY_MODEL_MUSIC: ${BETTY_MODEL_MUSIC:-llamusic/llamusic}\n BETTY_MODEL_IMAGE: ${BETTY_MODEL_IMAGE:-llava:34b}\n BETTY_MODEL_WRITE: ${BETTY_MODEL_WRITE:-HammerAI/mythomax-l2}\n BETTY_MODEL_CODE: ${BETTY_MODEL_CODE:-qwen2.5-coder:32b}\n BETTY_MODEL_RESEARCH: ${BETTY_MODEL_RESEARCH:-nemotron}\n BETTY_MODEL_FAST: ${BETTY_MODEL_FAST:-gemma2:27b}\n DEEPSEEK_API_KEY: ${DEEPSEEK_API_KEY:-}\n DEEPSEEK_BASE_URL: ${DEEPSEEK_BASE_URL:-https://api.deepseek.com}\n BETTY_DEEPSEEK_MODEL: ${BETTY_DEEPSEEK_MODEL:-deepseek-chat}\n BETTY_DEEPSEEK_REASONER_MODEL: ${BETTY_DEEPSEEK_REASONER_MODEL:-deepseek-reasoner}\n BETTY_DEEPSEEK_TIMEOUT_SECONDS: ${BETTY_DEEPSEEK_TIMEOUT_SECONDS:-120}\n BETTY_DEEPSEEK_CHAT_INPUT_COST_PER_1M_USD: ${BETTY_DEEPSEEK_CHAT_INPUT_COST_PER_1M_USD:-0.27}\n BETTY_DEEPSEEK_CHAT_OUTPUT_COST_PER_1M_USD: ${BETTY_DEEPSEEK_CHAT_OUTPUT_COST_PER_1M_USD:-1.10}\n BETTY_DEEPSEEK_REASONER_INPUT_COST_PER_1M_USD: ${BETTY_DEEPSEEK_REASONER_INPUT_COST_PER_1M_USD:-0.55}\n BETTY_DEEPSEEK_REASONER_OUTPUT_COST_PER_1M_USD: ${BETTY_DEEPSEEK_REASONER_OUTPUT_COST_PER_1M_USD:-2.19}\n KOKORO_ENABLED: ${KOKORO_ENABLED:-true}\n KOKORO_DEFAULT_VOICE: ${KOKORO_DEFAULT_VOICE:-af_bella}\n BETTY_OPERATOR_KOKORO_SPEED: ${BETTY_OPERATOR_KOKORO_SPEED:-1.0}\n BETTY_OPERATOR_KOKORO_DATA_DIR: ${BETTY_OPERATOR_KOKORO_DATA_DIR:-/app/data/kokoro}\n ELEVENLABS_ENABLED: ${ELEVENLABS_ENABLED:-true}\n TWILIO_ENABLED: ${TWILIO_ENABLED:-true}\n VAPI_ENABLED: ${VAPI_ENABLED:-true}\n BETTY_COST_DAILY_CAP_USD: ${BETTY_COST_DAILY_CAP_USD:-5}\n BETTY_COST_MONTHLY_CAP_USD: ${BETTY_COST_MONTHLY_CAP_USD:-50}\n BETTY_COST_WARNING_RATIO: ${BETTY_COST_WARNING_RATIO:-0.8}\n BETTY_ELEVENLABS_COST_PER_1K_CHARS_USD: ${BETTY_ELEVENLABS_COST_PER_1K_CHARS_USD:-0.30}\n BETTY_TWILIO_SMS_COST_USD: ${BETTY_TWILIO_SMS_COST_USD:-0.008}\n BETTY_VAPI_COST_PER_MINUTE_USD: ${BETTY_VAPI_COST_PER_MINUTE_USD:-0.075}\n ELEVENLABS_API_KEY: ${ELEVENLABS_API_KEY:-}\n ELEVENLABS_VOICE_ID: ${ELEVENLABS_VOICE_ID:-4tRn1lSkEn13EVTuqb0g}\n ELEVENLABS_MODEL_ID: ${ELEVENLABS_MODEL_ID:-eleven_turbo_v2_5}\n PIPER_VOICE_MODEL: ${PIPER_VOICE_MODEL:-en_US-lessac-medium}\n BETTY_OPERATOR_PIPER_DATA_DIR: ${BETTY_OPERATOR_PIPER_DATA_DIR:-/app/data/piper}\n GOOGLE_CALENDAR_CLIENT_ID: ${GOOGLE_CALENDAR_CLIENT_ID:-}\n GOOGLE_CALENDAR_CLIENT_SECRET: ${GOOGLE_CALENDAR_CLIENT_SECRET:-}\n GOOGLE_CALENDAR_OAUTH_REDIRECT_URI: ${GOOGLE_CALENDAR_OAUTH_REDIRECT_URI:-}\n BETTY_OPERATOR_RATE_LIMIT_WINDOW_SECONDS: ${BETTY_OPERATOR_RATE_LIMIT_WINDOW_SECONDS:-60}\n BETTY_OPERATOR_ELEVENLABS_RATE_LIMIT: ${BETTY_OPERATOR_ELEVENLABS_RATE_LIMIT:-20}\n BETTY_OPERATOR_TWILIO_RATE_LIMIT: ${BETTY_OPERATOR_TWILIO_RATE_LIMIT:-20}\n VAPI_API_KEY: ${VAPI_API_KEY:-}\n VAPI_ASSISTANT_ID: ${VAPI_ASSISTANT_ID:-}\n VAPI_WEBHOOK_SECRET: ${VAPI_WEBHOOK_SECRET:-}\n TWILIO_ACCOUNT_SID: ${TWILIO_ACCOUNT_SID:-}\n TWILIO_AUTH_TOKEN: ${TWILIO_AUTH_TOKEN:-}\n TWILIO_PHONE_NUMBER: ${TWILIO_PHONE_NUMBER:-+19492391845}\n ports:\n - \"0.0.0.0:${BETTY_OPERATOR_HOST_PORT:-18120}:8000\"\n volumes:\n - betty_operator_data:/app/data\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 15s\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n n8n:\n image: n8nio/n8n:latest\n container_name: betty-n8n\n depends_on:\n postgres:\n condition: service_healthy\n ollama:\n condition: service_healthy\n ollama-wrapper:\n condition: service_healthy\n betty-memory:\n condition: service_healthy\n betty-actions:\n condition: service_healthy\n betty-lookup:\n condition: service_healthy\n betty-operator:\n condition: service_healthy\n environment:\n N8N_HOST: ${N8N_HOST:-localhost}\n N8N_PORT: 5678\n N8N_PROTOCOL: ${N8N_PROTOCOL:-http}\n WEBHOOK_URL: ${WEBHOOK_URL:-http://localhost:15678/}\n GENERIC_TIMEZONE: ${GENERIC_TIMEZONE:-America/Los_Angeles}\n N8N_ENCRYPTION_KEY: ${N8N_ENCRYPTION_KEY:?set N8N_ENCRYPTION_KEY in .env}\n N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS: \"true\"\n N8N_DIAGNOSTICS_ENABLED: \"false\"\n N8N_PERSONALIZATION_ENABLED: \"false\"\n N8N_SECURE_COOKIE: \"false\"\n N8N_BASIC_AUTH_ACTIVE: ${N8N_BASIC_AUTH_ACTIVE:-true}\n N8N_BASIC_AUTH_USER: ${N8N_BASIC_AUTH_USER:?set N8N_BASIC_AUTH_USER in .env}\n N8N_BASIC_AUTH_PASSWORD: ${N8N_BASIC_AUTH_PASSWORD:?set N8N_BASIC_AUTH_PASSWORD in .env}\n DB_TYPE: postgresdb\n DB_POSTGRESDB_HOST: postgres\n DB_POSTGRESDB_PORT: 5432\n DB_POSTGRESDB_DATABASE: ${POSTGRES_DB:-n8n}\n DB_POSTGRESDB_USER: ${POSTGRES_USER:-n8n}\n DB_POSTGRESDB_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n OLLAMA_WRAPPER_URL: http://ollama-wrapper:8000\n BETTY_MEMORY_URL: http://betty-memory:8000\n BETTY_ACTIONS_URL: http://betty-actions:8000\n BETTY_LOOKUP_URL: http://betty-lookup:8000\n BETTY_OPERATOR_URL: http://betty-operator:8000\n ports:\n - \"127.0.0.1:${N8N_HOST_PORT:-15678}:5678\"\n volumes:\n - n8n_data:/home/node/.n8n\n restart: unless-stopped\n security_opt:\n - no-new-privileges:true\n networks:\n - betty-net\n\nvolumes:\n postgres_data:\n chroma_data:\n ollama_data:\n betty_operator_data:\n n8n_data:\n\nnetworks:\n betty-net:\n driver: bridge\n"}, "a4274351ad7c1dec5a7893663fcc341df54c8f13": {"t": 1785769599.9573977, "n": 17346, "h": "af6066b410428112", "c": "name: betty-stack\n\nservices:\n postgres:\n image: postgres:16-alpine\n container_name: betty-postgres\n environment:\n POSTGRES_USER: ${POSTGRES_USER:-n8n}\n POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n POSTGRES_DB: ${POSTGRES_DB:-n8n}\n volumes:\n - postgres_data:/var/lib/postgresql/data\n healthcheck:\n test: [\"CMD-SHELL\", \"pg_isready -U ${POSTGRES_USER:-n8n} -d ${POSTGRES_DB:-n8n}\"]\n interval: 10s\n timeout: 5s\n retries: 10\n start_period: 15s\n restart: unless-stopped\n security_opt:\n - no-new-privileges:true\n networks:\n - betty-net\n\n chroma:\n image: chromadb/chroma:0.5.23\n container_name: betty-chroma\n volumes:\n - chroma_data:/chroma/chroma\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/api/v1/heartbeat', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 15s\n restart: unless-stopped\n security_opt:\n - no-new-privileges:true\n networks:\n - betty-net\n\n ollama:\n image: ollama/ollama:latest\n container_name: betty-ollama\n ports:\n - \"127.0.0.1:${OLLAMA_HOST_PORT:-11435}:11434\"\n volumes:\n - ollama_data:/root/.ollama\n healthcheck:\n test: [\"CMD-SHELL\", \"ollama list >/dev/null 2>&1 || exit 1\"]\n interval: 20s\n timeout: 10s\n retries: 6\n start_period: 30s\n restart: unless-stopped\n security_opt:\n - no-new-privileges:true\n networks:\n - betty-net\n\n ollama-wrapper:\n build:\n context: ./ollama-wrapper\n image: betty/ollama-wrapper:local\n container_name: betty-ollama-wrapper\n depends_on:\n ollama:\n condition: service_healthy\n environment:\n OLLAMA_URL: http://ollama:11434\n API_USERNAME: ${WRAPPER_API_USERNAME:?set WRAPPER_API_USERNAME in .env}\n API_PASSWORD: ${WRAPPER_API_PASSWORD:?set WRAPPER_API_PASSWORD in .env}\n API_BEARER_TOKEN: ${WRAPPER_API_BEARER_TOKEN:-}\n WRAPPER_REQUEST_TIMEOUT_SECONDS: ${WRAPPER_REQUEST_TIMEOUT_SECONDS:-120}\n LOG_LEVEL: ${WRAPPER_LOG_LEVEL:-info}\n ports:\n - \"127.0.0.1:${WRAPPER_HOST_PORT:-18000}:8000\"\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 10s\n read_only: true\n tmpfs:\n - /tmp\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n betty-memory:\n build:\n context: ./services/memory\n image: betty/memory-service:local\n container_name: betty-memory\n depends_on:\n postgres:\n condition: service_healthy\n chroma:\n condition: service_healthy\n ollama:\n condition: service_healthy\n environment:\n BETTY_MEMORY_POSTGRES_HOST: postgres\n BETTY_MEMORY_POSTGRES_PORT: 5432\n BETTY_MEMORY_POSTGRES_DB: ${POSTGRES_DB:-n8n}\n BETTY_MEMORY_POSTGRES_USER: ${POSTGRES_USER:-n8n}\n BETTY_MEMORY_POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n BETTY_MEMORY_CHROMA_HOST: chroma\n BETTY_MEMORY_CHROMA_PORT: 8000\n BETTY_MEMORY_COLLECTION: ${BETTY_MEMORY_COLLECTION:-betty-v1-facts}\n BETTY_MEMORY_OLLAMA_URL: http://ollama:11434\n BETTY_MEMORY_EMBED_MODEL: ${BETTY_MEMORY_EMBED_MODEL:-nomic-embed-text}\n BETTY_MEMORY_SEMANTIC_DISTANCE_THRESHOLD: ${BETTY_MEMORY_SEMANTIC_DISTANCE_THRESHOLD:-0.45}\n BETTY_MEMORY_LOG_LEVEL: ${BETTY_MEMORY_LOG_LEVEL:-info}\n BETTY_MEMORY_DEFAULT_TIMEZONE: ${GENERIC_TIMEZONE:-America/Los_Angeles}\n BETTY_OWNER_NAME: ${BETTY_OWNER_NAME:-Vincent}\n BETTY_HOME_CITY: ${BETTY_HOME_CITY:-Corona Del Mar, CA}\n ports:\n - \"127.0.0.1:${BETTY_MEMORY_HOST_PORT:-18100}:8000\"\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 15s\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n betty-actions:\n build:\n context: ./services/actions\n image: betty/actions-service:local\n container_name: betty-actions\n depends_on:\n postgres:\n condition: service_healthy\n environment:\n BETTY_ACTIONS_POSTGRES_HOST: postgres\n BETTY_ACTIONS_POSTGRES_PORT: 5432\n BETTY_ACTIONS_POSTGRES_DB: ${POSTGRES_DB:-n8n}\n BETTY_ACTIONS_POSTGRES_USER: ${POSTGRES_USER:-n8n}\n BETTY_ACTIONS_POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n BETTY_ACTIONS_DEFAULT_TIMEZONE: ${GENERIC_TIMEZONE:-America/Los_Angeles}\n GOOGLE_CALENDAR_CLIENT_ID: ${GOOGLE_CALENDAR_CLIENT_ID:-}\n GOOGLE_CALENDAR_CLIENT_SECRET: ${GOOGLE_CALENDAR_CLIENT_SECRET:-}\n ports:\n - \"127.0.0.1:${BETTY_ACTIONS_HOST_PORT:-18110}:8000\"\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 15s\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n betty-lookup:\n build:\n context: ./services/lookup\n image: betty/lookup-service:local\n container_name: betty-lookup\n depends_on:\n postgres:\n condition: service_healthy\n ollama-wrapper:\n condition: service_healthy\n environment:\n BETTY_LOOKUP_POSTGRES_HOST: postgres\n BETTY_LOOKUP_POSTGRES_PORT: 5432\n BETTY_LOOKUP_POSTGRES_DB: ${POSTGRES_DB:-n8n}\n BETTY_LOOKUP_POSTGRES_USER: ${POSTGRES_USER:-n8n}\n BETTY_LOOKUP_POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n BETTY_LOOKUP_DEFAULT_TIMEZONE: ${GENERIC_TIMEZONE:-America/Los_Angeles}\n BETTY_OWNER_NAME: ${BETTY_OWNER_NAME:-Vincent}\n BETTY_HOME_CITY: ${BETTY_HOME_CITY:-Corona Del Mar, CA}\n BRAVE_ENABLED: ${BRAVE_ENABLED:-false}\n SEARXNG_ENABLED: ${SEARXNG_ENABLED:-true}\n SEARXNG_BASE_URL: ${SEARXNG_BASE_URL:-http://host.docker.internal:8080}\n BETTY_COST_DAILY_CAP_USD: ${BETTY_COST_DAILY_CAP_USD:-5}\n BETTY_COST_MONTHLY_CAP_USD: ${BETTY_COST_MONTHLY_CAP_USD:-50}\n BETTY_COST_WARNING_RATIO: ${BETTY_COST_WARNING_RATIO:-0.8}\n BRAVE_SEARCH_API_KEY: ${BRAVE_SEARCH_API_KEY:-}\n BETTY_LOOKUP_WRAPPER_URL: http://ollama-wrapper:8000/v1/chat/completions\n BETTY_LOOKUP_WRAPPER_USERNAME: ${WRAPPER_API_USERNAME:?set WRAPPER_API_USERNAME in .env}\n BETTY_LOOKUP_WRAPPER_PASSWORD: ${WRAPPER_API_PASSWORD:?set WRAPPER_API_PASSWORD in .env}\n BETTY_LOOKUP_WRAPPER_BEARER_TOKEN: ${WRAPPER_API_BEARER_TOKEN:-}\n BETTY_LOOKUP_MODEL: ${BETTY_LOOKUP_MODEL:-}\n BETTY_LOOKUP_WRAPPER_TIMEOUT_SECONDS: ${WRAPPER_REQUEST_TIMEOUT_SECONDS:-120}\n ports:\n - \"127.0.0.1:${BETTY_LOOKUP_HOST_PORT:-18130}:8000\"\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 15s\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n betty-brain:\n build:\n context: ./services/brain\n image: betty/brain-service:local\n container_name: betty-brain\n depends_on:\n postgres:\n condition: service_healthy\n environment:\n BETTY_BRAIN_POSTGRES_HOST: postgres\n BETTY_BRAIN_POSTGRES_PORT: 5432\n BETTY_BRAIN_POSTGRES_DB: ${POSTGRES_DB:-n8n}\n BETTY_BRAIN_POSTGRES_USER: ${POSTGRES_USER:-n8n}\n BETTY_BRAIN_POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n BETTY_BRAIN_MODEL: ${BETTY_BRAIN_MODEL:-claude-haiku-4-5}\n BETTY_BRAIN_MAX_TOKENS: ${BETTY_BRAIN_MAX_TOKENS:-2000}\n ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:?set ANTHROPIC_API_KEY in .env}\n BETTY_COST_DAILY_CAP_USD: ${BETTY_COST_DAILY_CAP_USD:-1}\n BETTY_COST_MONTHLY_CAP_USD: ${BETTY_COST_MONTHLY_CAP_USD:-30}\n BETTY_COST_WARNING_RATIO: ${BETTY_COST_WARNING_RATIO:-0.8}\n BETTY_OWNER_NAME: ${BETTY_OWNER_NAME:-Vincent}\n BETTY_HOME_CITY: ${BETTY_HOME_CITY:-Rancho Santa Margarita, CA}\n ports:\n - \"127.0.0.1:${BETTY_BRAIN_HOST_PORT:-18140}:8000\"\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 15s\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n betty-operator:\n build:\n context: ./services/operator\n image: betty/operator-service:local\n container_name: betty-operator\n depends_on:\n postgres:\n condition: service_healthy\n betty-memory:\n condition: service_healthy\n betty-actions:\n condition: service_healthy\n betty-lookup:\n condition: service_healthy\n environment:\n BETTY_OPERATOR_POSTGRES_HOST: postgres\n BETTY_OPERATOR_POSTGRES_PORT: 5432\n BETTY_OPERATOR_POSTGRES_DB: ${POSTGRES_DB:-n8n}\n BETTY_OPERATOR_POSTGRES_USER: ${POSTGRES_USER:-n8n}\n BETTY_OPERATOR_POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n BETTY_OPERATOR_MEMORY_URL: http://betty-memory:8000\n BETTY_OPERATOR_ACTIONS_URL: http://betty-actions:8000\n BETTY_OPERATOR_LOOKUP_URL: http://betty-lookup:8000\n BETTY_OPERATOR_WRAPPER_URL: http://ollama-wrapper:8000/v1/chat/completions\n BETTY_OPERATOR_WRAPPER_USERNAME: ${WRAPPER_API_USERNAME:?set WRAPPER_API_USERNAME in .env}\n BETTY_OPERATOR_WRAPPER_PASSWORD: ${WRAPPER_API_PASSWORD:?set WRAPPER_API_PASSWORD in .env}\n BETTY_OPERATOR_WRAPPER_BEARER_TOKEN: ${WRAPPER_API_BEARER_TOKEN:-}\n BETTY_OPERATOR_WRAPPER_TIMEOUT_SECONDS: ${WRAPPER_REQUEST_TIMEOUT_SECONDS:-120}\n BETTY_OPERATOR_DEFAULT_TIMEZONE: ${GENERIC_TIMEZONE:-America/Los_Angeles}\n BETTY_PUBLIC_BASE_URL: ${BETTY_PUBLIC_BASE_URL:-https://YOUR_PUBLIC_URL}\n BETTY_OWNER_PHONE: ${BETTY_OWNER_PHONE:-}\n BETTY_OWNER_NAME: ${BETTY_OWNER_NAME:-Vincent}\n BETTY_HOME_CITY: ${BETTY_HOME_CITY:-Corona Del Mar, CA}\n BETTY_OPERATOR_STT_PROVIDER: ${BETTY_OPERATOR_STT_PROVIDER:-whisper_local}\n BETTY_OPERATOR_WHISPER_MODEL: ${BETTY_OPERATOR_WHISPER_MODEL:-base.en}\n BETTY_OPERATOR_WHISPER_DEVICE: ${BETTY_OPERATOR_WHISPER_DEVICE:-cpu}\n BETTY_OPERATOR_WHISPER_COMPUTE_TYPE: ${BETTY_OPERATOR_WHISPER_COMPUTE_TYPE:-int8}\n BETTY_OPERATOR_WHISPER_DATA_DIR: ${BETTY_OPERATOR_WHISPER_DATA_DIR:-/app/data/whisper}\n BETTY_MODEL_MODE: ${BETTY_MODEL_MODE:-default}\n BETTY_DEFAULT_CHAT_PROVIDER: ${BETTY_DEFAULT_CHAT_PROVIDER:-auto}\n BETTY_DEFAULT_CHAT_MODEL: ${BETTY_DEFAULT_CHAT_MODEL:-deepseek-chat}\n BETTY_DEFAULT_LOCAL_CHAT_MODEL: ${BETTY_DEFAULT_LOCAL_CHAT_MODEL:-llama3.1:8b}\n BETTY_MODEL_DOLPHIN: ${BETTY_MODEL_DOLPHIN:-dolphin-llama3:70b}\n BETTY_MODEL_MEDICAL: ${BETTY_MODEL_MEDICAL:-medllama2}\n BETTY_MODEL_MUSIC: ${BETTY_MODEL_MUSIC:-llamusic/llamusic}\n BETTY_MODEL_IMAGE: ${BETTY_MODEL_IMAGE:-llava:34b}\n BETTY_MODEL_WRITE: ${BETTY_MODEL_WRITE:-HammerAI/mythomax-l2}\n BETTY_MODEL_CODE: ${BETTY_MODEL_CODE:-qwen2.5-coder:32b}\n BETTY_MODEL_RESEARCH: ${BETTY_MODEL_RESEARCH:-nemotron}\n BETTY_MODEL_FAST: ${BETTY_MODEL_FAST:-gemma2:27b}\n DEEPSEEK_API_KEY: ${DEEPSEEK_API_KEY:-}\n DEEPSEEK_BASE_URL: ${DEEPSEEK_BASE_URL:-https://api.deepseek.com}\n BETTY_DEEPSEEK_MODEL: ${BETTY_DEEPSEEK_MODEL:-deepseek-chat}\n BETTY_DEEPSEEK_REASONER_MODEL: ${BETTY_DEEPSEEK_REASONER_MODEL:-deepseek-reasoner}\n BETTY_DEEPSEEK_TIMEOUT_SECONDS: ${BETTY_DEEPSEEK_TIMEOUT_SECONDS:-120}\n BETTY_DEEPSEEK_CHAT_INPUT_COST_PER_1M_USD: ${BETTY_DEEPSEEK_CHAT_INPUT_COST_PER_1M_USD:-0.27}\n BETTY_DEEPSEEK_CHAT_OUTPUT_COST_PER_1M_USD: ${BETTY_DEEPSEEK_CHAT_OUTPUT_COST_PER_1M_USD:-1.10}\n BETTY_DEEPSEEK_REASONER_INPUT_COST_PER_1M_USD: ${BETTY_DEEPSEEK_REASONER_INPUT_COST_PER_1M_USD:-0.55}\n BETTY_DEEPSEEK_REASONER_OUTPUT_COST_PER_1M_USD: ${BETTY_DEEPSEEK_REASONER_OUTPUT_COST_PER_1M_USD:-2.19}\n KOKORO_ENABLED: ${KOKORO_ENABLED:-true}\n KOKORO_DEFAULT_VOICE: ${KOKORO_DEFAULT_VOICE:-af_bella}\n BETTY_OPERATOR_KOKORO_SPEED: ${BETTY_OPERATOR_KOKORO_SPEED:-1.0}\n BETTY_OPERATOR_KOKORO_DATA_DIR: ${BETTY_OPERATOR_KOKORO_DATA_DIR:-/app/data/kokoro}\n ELEVENLABS_ENABLED: ${ELEVENLABS_ENABLED:-true}\n TWILIO_ENABLED: ${TWILIO_ENABLED:-true}\n VAPI_ENABLED: ${VAPI_ENABLED:-true}\n BETTY_COST_DAILY_CAP_USD: ${BETTY_COST_DAILY_CAP_USD:-5}\n BETTY_COST_MONTHLY_CAP_USD: ${BETTY_COST_MONTHLY_CAP_USD:-50}\n BETTY_COST_WARNING_RATIO: ${BETTY_COST_WARNING_RATIO:-0.8}\n BETTY_ELEVENLABS_COST_PER_1K_CHARS_USD: ${BETTY_ELEVENLABS_COST_PER_1K_CHARS_USD:-0.30}\n BETTY_TWILIO_SMS_COST_USD: ${BETTY_TWILIO_SMS_COST_USD:-0.008}\n BETTY_VAPI_COST_PER_MINUTE_USD: ${BETTY_VAPI_COST_PER_MINUTE_USD:-0.075}\n ELEVENLABS_API_KEY: ${ELEVENLABS_API_KEY:-}\n ELEVENLABS_VOICE_ID: ${ELEVENLABS_VOICE_ID:-4tRn1lSkEn13EVTuqb0g}\n ELEVENLABS_MODEL_ID: ${ELEVENLABS_MODEL_ID:-eleven_turbo_v2_5}\n PIPER_VOICE_MODEL: ${PIPER_VOICE_MODEL:-en_US-lessac-medium}\n BETTY_OPERATOR_PIPER_DATA_DIR: ${BETTY_OPERATOR_PIPER_DATA_DIR:-/app/data/piper}\n GOOGLE_CALENDAR_CLIENT_ID: ${GOOGLE_CALENDAR_CLIENT_ID:-}\n GOOGLE_CALENDAR_CLIENT_SECRET: ${GOOGLE_CALENDAR_CLIENT_SECRET:-}\n GOOGLE_CALENDAR_OAUTH_REDIRECT_URI: ${GOOGLE_CALENDAR_OAUTH_REDIRECT_URI:-}\n BETTY_OPERATOR_RATE_LIMIT_WINDOW_SECONDS: ${BETTY_OPERATOR_RATE_LIMIT_WINDOW_SECONDS:-60}\n BETTY_OPERATOR_ELEVENLABS_RATE_LIMIT: ${BETTY_OPERATOR_ELEVENLABS_RATE_LIMIT:-20}\n BETTY_OPERATOR_TWILIO_RATE_LIMIT: ${BETTY_OPERATOR_TWILIO_RATE_LIMIT:-20}\n VAPI_API_KEY: ${VAPI_API_KEY:-}\n VAPI_ASSISTANT_ID: ${VAPI_ASSISTANT_ID:-}\n VAPI_WEBHOOK_SECRET: ${VAPI_WEBHOOK_SECRET:-}\n TWILIO_ACCOUNT_SID: ${TWILIO_ACCOUNT_SID:-}\n TWILIO_AUTH_TOKEN: ${TWILIO_AUTH_TOKEN:-}\n TWILIO_PHONE_NUMBER: ${TWILIO_PHONE_NUMBER:-+19492391845}\n ports:\n - \"0.0.0.0:${BETTY_OPERATOR_HOST_PORT:-18120}:8000\"\n volumes:\n - betty_operator_data:/app/data\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 15s\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n n8n:\n image: n8nio/n8n:latest\n container_name: betty-n8n\n depends_on:\n postgres:\n condition: service_healthy\n ollama:\n condition: service_healthy\n ollama-wrapper:\n condition: service_healthy\n betty-memory:\n condition: service_healthy\n betty-actions:\n condition: service_healthy\n betty-lookup:\n condition: service_healthy\n betty-operator:\n condition: service_healthy\n environment:\n N8N_HOST: ${N8N_HOST:-localhost}\n N8N_PORT: 5678\n N8N_PROTOCOL: ${N8N_PROTOCOL:-http}\n WEBHOOK_URL: ${WEBHOOK_URL:-http://localhost:15678/}\n GENERIC_TIMEZONE: ${GENERIC_TIMEZONE:-America/Los_Angeles}\n N8N_ENCRYPTION_KEY: ${N8N_ENCRYPTION_KEY:?set N8N_ENCRYPTION_KEY in .env}\n N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS: \"true\"\n N8N_DIAGNOSTICS_ENABLED: \"false\"\n N8N_PERSONALIZATION_ENABLED: \"false\"\n N8N_SECURE_COOKIE: \"false\"\n N8N_BASIC_AUTH_ACTIVE: ${N8N_BASIC_AUTH_ACTIVE:-true}\n N8N_BASIC_AUTH_USER: ${N8N_BASIC_AUTH_USER:?set N8N_BASIC_AUTH_USER in .env}\n N8N_BASIC_AUTH_PASSWORD: ${N8N_BASIC_AUTH_PASSWORD:?set N8N_BASIC_AUTH_PASSWORD in .env}\n DB_TYPE: postgresdb\n DB_POSTGRESDB_HOST: postgres\n DB_POSTGRESDB_PORT: 5432\n DB_POSTGRESDB_DATABASE: ${POSTGRES_DB:-n8n}\n DB_POSTGRESDB_USER: ${POSTGRES_USER:-n8n}\n DB_POSTGRESDB_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n OLLAMA_WRAPPER_URL: http://ollama-wrapper:8000\n BETTY_MEMORY_URL: http://betty-memory:8000\n BETTY_ACTIONS_URL: http://betty-actions:8000\n BETTY_LOOKUP_URL: http://betty-lookup:8000\n BETTY_OPERATOR_URL: http://betty-operator:8000\n ports:\n - \"127.0.0.1:${N8N_HOST_PORT:-15678}:5678\"\n volumes:\n - n8n_data:/home/node/.n8n\n restart: unless-stopped\n security_opt:\n - no-new-privileges:true\n networks:\n - betty-net\n\nvolumes:\n postgres_data:\n chroma_data:\n ollama_data:\n betty_operator_data:\n n8n_data:\n\nnetworks:\n betty-net:\n driver: bridge\n"}, "e0496557da5335b6aa70e7720fc059c60980d89b": {"t": 1785769601.3827682, "n": 15870, "h": "adab18bf02a713fc", "c": "name: betty-stack\n\nservices:\n postgres:\n image: postgres:16-alpine\n container_name: betty-postgres\n environment:\n POSTGRES_USER: ${POSTGRES_USER:-n8n}\n POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n POSTGRES_DB: ${POSTGRES_DB:-n8n}\n volumes:\n - postgres_data:/var/lib/postgresql/data\n healthcheck:\n test: [\"CMD-SHELL\", \"pg_isready -U ${POSTGRES_USER:-n8n} -d ${POSTGRES_DB:-n8n}\"]\n interval: 10s\n timeout: 5s\n retries: 10\n start_period: 15s\n restart: unless-stopped\n security_opt:\n - no-new-privileges:true\n networks:\n - betty-net\n\n chroma:\n image: chromadb/chroma:0.5.23\n container_name: betty-chroma\n volumes:\n - chroma_data:/chroma/chroma\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/api/v1/heartbeat', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 15s\n restart: unless-stopped\n security_opt:\n - no-new-privileges:true\n networks:\n - betty-net\n\n ollama:\n image: ollama/ollama:latest\n container_name: betty-ollama\n ports:\n - \"127.0.0.1:${OLLAMA_HOST_PORT:-11435}:11434\"\n volumes:\n - ollama_data:/root/.ollama\n healthcheck:\n test: [\"CMD-SHELL\", \"ollama list >/dev/null 2>&1 || exit 1\"]\n interval: 20s\n timeout: 10s\n retries: 6\n start_period: 30s\n restart: unless-stopped\n security_opt:\n - no-new-privileges:true\n networks:\n - betty-net\n\n ollama-wrapper:\n build:\n context: ./ollama-wrapper\n image: betty/ollama-wrapper:local\n container_name: betty-ollama-wrapper\n depends_on:\n ollama:\n condition: service_healthy\n environment:\n OLLAMA_URL: http://ollama:11434\n API_USERNAME: ${WRAPPER_API_USERNAME:?set WRAPPER_API_USERNAME in .env}\n API_PASSWORD: ${WRAPPER_API_PASSWORD:?set WRAPPER_API_PASSWORD in .env}\n API_BEARER_TOKEN: ${WRAPPER_API_BEARER_TOKEN:-}\n WRAPPER_REQUEST_TIMEOUT_SECONDS: ${WRAPPER_REQUEST_TIMEOUT_SECONDS:-120}\n LOG_LEVEL: ${WRAPPER_LOG_LEVEL:-info}\n ports:\n - \"127.0.0.1:${WRAPPER_HOST_PORT:-18000}:8000\"\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 10s\n read_only: true\n tmpfs:\n - /tmp\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n betty-memory:\n build:\n context: ./services/memory\n image: betty/memory-service:local\n container_name: betty-memory\n depends_on:\n postgres:\n condition: service_healthy\n chroma:\n condition: service_healthy\n ollama:\n condition: service_healthy\n environment:\n BETTY_MEMORY_POSTGRES_HOST: postgres\n BETTY_MEMORY_POSTGRES_PORT: 5432\n BETTY_MEMORY_POSTGRES_DB: ${POSTGRES_DB:-n8n}\n BETTY_MEMORY_POSTGRES_USER: ${POSTGRES_USER:-n8n}\n BETTY_MEMORY_POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n BETTY_MEMORY_CHROMA_HOST: chroma\n BETTY_MEMORY_CHROMA_PORT: 8000\n BETTY_MEMORY_COLLECTION: ${BETTY_MEMORY_COLLECTION:-betty-v1-facts}\n BETTY_MEMORY_OLLAMA_URL: http://ollama:11434\n BETTY_MEMORY_EMBED_MODEL: ${BETTY_MEMORY_EMBED_MODEL:-nomic-embed-text}\n BETTY_MEMORY_SEMANTIC_DISTANCE_THRESHOLD: ${BETTY_MEMORY_SEMANTIC_DISTANCE_THRESHOLD:-0.45}\n BETTY_MEMORY_LOG_LEVEL: ${BETTY_MEMORY_LOG_LEVEL:-info}\n BETTY_MEMORY_DEFAULT_TIMEZONE: ${GENERIC_TIMEZONE:-America/Los_Angeles}\n BETTY_OWNER_NAME: ${BETTY_OWNER_NAME:-Vincent}\n BETTY_HOME_CITY: ${BETTY_HOME_CITY:-Corona Del Mar, CA}\n ports:\n - \"127.0.0.1:${BETTY_MEMORY_HOST_PORT:-18100}:8000\"\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 15s\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n betty-actions:\n build:\n context: ./services/actions\n image: betty/actions-service:local\n container_name: betty-actions\n depends_on:\n postgres:\n condition: service_healthy\n environment:\n BETTY_ACTIONS_POSTGRES_HOST: postgres\n BETTY_ACTIONS_POSTGRES_PORT: 5432\n BETTY_ACTIONS_POSTGRES_DB: ${POSTGRES_DB:-n8n}\n BETTY_ACTIONS_POSTGRES_USER: ${POSTGRES_USER:-n8n}\n BETTY_ACTIONS_POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n BETTY_ACTIONS_DEFAULT_TIMEZONE: ${GENERIC_TIMEZONE:-America/Los_Angeles}\n GOOGLE_CALENDAR_CLIENT_ID: ${GOOGLE_CALENDAR_CLIENT_ID:-}\n GOOGLE_CALENDAR_CLIENT_SECRET: ${GOOGLE_CALENDAR_CLIENT_SECRET:-}\n ports:\n - \"127.0.0.1:${BETTY_ACTIONS_HOST_PORT:-18110}:8000\"\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 15s\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n betty-lookup:\n build:\n context: ./services/lookup\n image: betty/lookup-service:local\n container_name: betty-lookup\n depends_on:\n postgres:\n condition: service_healthy\n ollama-wrapper:\n condition: service_healthy\n environment:\n BETTY_LOOKUP_POSTGRES_HOST: postgres\n BETTY_LOOKUP_POSTGRES_PORT: 5432\n BETTY_LOOKUP_POSTGRES_DB: ${POSTGRES_DB:-n8n}\n BETTY_LOOKUP_POSTGRES_USER: ${POSTGRES_USER:-n8n}\n BETTY_LOOKUP_POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n BETTY_LOOKUP_DEFAULT_TIMEZONE: ${GENERIC_TIMEZONE:-America/Los_Angeles}\n BETTY_OWNER_NAME: ${BETTY_OWNER_NAME:-Vincent}\n BETTY_HOME_CITY: ${BETTY_HOME_CITY:-Corona Del Mar, CA}\n BRAVE_ENABLED: ${BRAVE_ENABLED:-false}\n SEARXNG_ENABLED: ${SEARXNG_ENABLED:-true}\n SEARXNG_BASE_URL: ${SEARXNG_BASE_URL:-http://host.docker.internal:8080}\n BETTY_COST_DAILY_CAP_USD: ${BETTY_COST_DAILY_CAP_USD:-5}\n BETTY_COST_MONTHLY_CAP_USD: ${BETTY_COST_MONTHLY_CAP_USD:-50}\n BETTY_COST_WARNING_RATIO: ${BETTY_COST_WARNING_RATIO:-0.8}\n BRAVE_SEARCH_API_KEY: ${BRAVE_SEARCH_API_KEY:-}\n BETTY_LOOKUP_WRAPPER_URL: http://ollama-wrapper:8000/v1/chat/completions\n BETTY_LOOKUP_WRAPPER_USERNAME: ${WRAPPER_API_USERNAME:?set WRAPPER_API_USERNAME in .env}\n BETTY_LOOKUP_WRAPPER_PASSWORD: ${WRAPPER_API_PASSWORD:?set WRAPPER_API_PASSWORD in .env}\n BETTY_LOOKUP_WRAPPER_BEARER_TOKEN: ${WRAPPER_API_BEARER_TOKEN:-}\n BETTY_LOOKUP_MODEL: ${BETTY_LOOKUP_MODEL:-}\n BETTY_LOOKUP_WRAPPER_TIMEOUT_SECONDS: ${WRAPPER_REQUEST_TIMEOUT_SECONDS:-120}\n ports:\n - \"127.0.0.1:${BETTY_LOOKUP_HOST_PORT:-18130}:8000\"\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 15s\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n betty-operator:\n build:\n context: ./services/operator\n image: betty/operator-service:local\n container_name: betty-operator\n depends_on:\n postgres:\n condition: service_healthy\n betty-memory:\n condition: service_healthy\n betty-actions:\n condition: service_healthy\n betty-lookup:\n condition: service_healthy\n environment:\n BETTY_OPERATOR_POSTGRES_HOST: postgres\n BETTY_OPERATOR_POSTGRES_PORT: 5432\n BETTY_OPERATOR_POSTGRES_DB: ${POSTGRES_DB:-n8n}\n BETTY_OPERATOR_POSTGRES_USER: ${POSTGRES_USER:-n8n}\n BETTY_OPERATOR_POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n BETTY_OPERATOR_MEMORY_URL: http://betty-memory:8000\n BETTY_OPERATOR_ACTIONS_URL: http://betty-actions:8000\n BETTY_OPERATOR_LOOKUP_URL: http://betty-lookup:8000\n BETTY_OPERATOR_WRAPPER_URL: http://ollama-wrapper:8000/v1/chat/completions\n BETTY_OPERATOR_WRAPPER_USERNAME: ${WRAPPER_API_USERNAME:?set WRAPPER_API_USERNAME in .env}\n BETTY_OPERATOR_WRAPPER_PASSWORD: ${WRAPPER_API_PASSWORD:?set WRAPPER_API_PASSWORD in .env}\n BETTY_OPERATOR_WRAPPER_BEARER_TOKEN: ${WRAPPER_API_BEARER_TOKEN:-}\n BETTY_OPERATOR_WRAPPER_TIMEOUT_SECONDS: ${WRAPPER_REQUEST_TIMEOUT_SECONDS:-120}\n BETTY_OPERATOR_DEFAULT_TIMEZONE: ${GENERIC_TIMEZONE:-America/Los_Angeles}\n BETTY_PUBLIC_BASE_URL: ${BETTY_PUBLIC_BASE_URL:-https://YOUR_PUBLIC_URL}\n BETTY_OWNER_PHONE: ${BETTY_OWNER_PHONE:-}\n BETTY_OWNER_NAME: ${BETTY_OWNER_NAME:-Vincent}\n BETTY_HOME_CITY: ${BETTY_HOME_CITY:-Corona Del Mar, CA}\n BETTY_OPERATOR_STT_PROVIDER: ${BETTY_OPERATOR_STT_PROVIDER:-whisper_local}\n BETTY_OPERATOR_WHISPER_MODEL: ${BETTY_OPERATOR_WHISPER_MODEL:-base.en}\n BETTY_OPERATOR_WHISPER_DEVICE: ${BETTY_OPERATOR_WHISPER_DEVICE:-cpu}\n BETTY_OPERATOR_WHISPER_COMPUTE_TYPE: ${BETTY_OPERATOR_WHISPER_COMPUTE_TYPE:-int8}\n BETTY_OPERATOR_WHISPER_DATA_DIR: ${BETTY_OPERATOR_WHISPER_DATA_DIR:-/app/data/whisper}\n BETTY_MODEL_MODE: ${BETTY_MODEL_MODE:-default}\n BETTY_DEFAULT_CHAT_PROVIDER: ${BETTY_DEFAULT_CHAT_PROVIDER:-auto}\n BETTY_DEFAULT_CHAT_MODEL: ${BETTY_DEFAULT_CHAT_MODEL:-deepseek-chat}\n BETTY_DEFAULT_LOCAL_CHAT_MODEL: ${BETTY_DEFAULT_LOCAL_CHAT_MODEL:-llama3.1:8b}\n BETTY_MODEL_DOLPHIN: ${BETTY_MODEL_DOLPHIN:-dolphin-llama3:70b}\n BETTY_MODEL_MEDICAL: ${BETTY_MODEL_MEDICAL:-medllama2}\n BETTY_MODEL_MUSIC: ${BETTY_MODEL_MUSIC:-llamusic/llamusic}\n BETTY_MODEL_IMAGE: ${BETTY_MODEL_IMAGE:-llava:34b}\n BETTY_MODEL_WRITE: ${BETTY_MODEL_WRITE:-HammerAI/mythomax-l2}\n BETTY_MODEL_CODE: ${BETTY_MODEL_CODE:-qwen2.5-coder:32b}\n BETTY_MODEL_RESEARCH: ${BETTY_MODEL_RESEARCH:-nemotron}\n BETTY_MODEL_FAST: ${BETTY_MODEL_FAST:-gemma2:27b}\n DEEPSEEK_API_KEY: ${DEEPSEEK_API_KEY:-}\n DEEPSEEK_BASE_URL: ${DEEPSEEK_BASE_URL:-https://api.deepseek.com}\n BETTY_DEEPSEEK_MODEL: ${BETTY_DEEPSEEK_MODEL:-deepseek-chat}\n BETTY_DEEPSEEK_REASONER_MODEL: ${BETTY_DEEPSEEK_REASONER_MODEL:-deepseek-reasoner}\n BETTY_DEEPSEEK_TIMEOUT_SECONDS: ${BETTY_DEEPSEEK_TIMEOUT_SECONDS:-120}\n BETTY_DEEPSEEK_CHAT_INPUT_COST_PER_1M_USD: ${BETTY_DEEPSEEK_CHAT_INPUT_COST_PER_1M_USD:-0.27}\n BETTY_DEEPSEEK_CHAT_OUTPUT_COST_PER_1M_USD: ${BETTY_DEEPSEEK_CHAT_OUTPUT_COST_PER_1M_USD:-1.10}\n BETTY_DEEPSEEK_REASONER_INPUT_COST_PER_1M_USD: ${BETTY_DEEPSEEK_REASONER_INPUT_COST_PER_1M_USD:-0.55}\n BETTY_DEEPSEEK_REASONER_OUTPUT_COST_PER_1M_USD: ${BETTY_DEEPSEEK_REASONER_OUTPUT_COST_PER_1M_USD:-2.19}\n KOKORO_ENABLED: ${KOKORO_ENABLED:-true}\n KOKORO_DEFAULT_VOICE: ${KOKORO_DEFAULT_VOICE:-af_bella}\n BETTY_OPERATOR_KOKORO_SPEED: ${BETTY_OPERATOR_KOKORO_SPEED:-1.0}\n BETTY_OPERATOR_KOKORO_DATA_DIR: ${BETTY_OPERATOR_KOKORO_DATA_DIR:-/app/data/kokoro}\n ELEVENLABS_ENABLED: ${ELEVENLABS_ENABLED:-true}\n TWILIO_ENABLED: ${TWILIO_ENABLED:-true}\n VAPI_ENABLED: ${VAPI_ENABLED:-true}\n BETTY_COST_DAILY_CAP_USD: ${BETTY_COST_DAILY_CAP_USD:-5}\n BETTY_COST_MONTHLY_CAP_USD: ${BETTY_COST_MONTHLY_CAP_USD:-50}\n BETTY_COST_WARNING_RATIO: ${BETTY_COST_WARNING_RATIO:-0.8}\n BETTY_ELEVENLABS_COST_PER_1K_CHARS_USD: ${BETTY_ELEVENLABS_COST_PER_1K_CHARS_USD:-0.30}\n BETTY_TWILIO_SMS_COST_USD: ${BETTY_TWILIO_SMS_COST_USD:-0.008}\n BETTY_VAPI_COST_PER_MINUTE_USD: ${BETTY_VAPI_COST_PER_MINUTE_USD:-0.075}\n ELEVENLABS_API_KEY: ${ELEVENLABS_API_KEY:-}\n ELEVENLABS_VOICE_ID: ${ELEVENLABS_VOICE_ID:-4tRn1lSkEn13EVTuqb0g}\n ELEVENLABS_MODEL_ID: ${ELEVENLABS_MODEL_ID:-eleven_turbo_v2_5}\n PIPER_VOICE_MODEL: ${PIPER_VOICE_MODEL:-en_US-lessac-medium}\n BETTY_OPERATOR_PIPER_DATA_DIR: ${BETTY_OPERATOR_PIPER_DATA_DIR:-/app/data/piper}\n GOOGLE_CALENDAR_CLIENT_ID: ${GOOGLE_CALENDAR_CLIENT_ID:-}\n GOOGLE_CALENDAR_CLIENT_SECRET: ${GOOGLE_CALENDAR_CLIENT_SECRET:-}\n GOOGLE_CALENDAR_OAUTH_REDIRECT_URI: ${GOOGLE_CALENDAR_OAUTH_REDIRECT_URI:-}\n BETTY_OPERATOR_RATE_LIMIT_WINDOW_SECONDS: ${BETTY_OPERATOR_RATE_LIMIT_WINDOW_SECONDS:-60}\n BETTY_OPERATOR_ELEVENLABS_RATE_LIMIT: ${BETTY_OPERATOR_ELEVENLABS_RATE_LIMIT:-20}\n BETTY_OPERATOR_TWILIO_RATE_LIMIT: ${BETTY_OPERATOR_TWILIO_RATE_LIMIT:-20}\n VAPI_API_KEY: ${VAPI_API_KEY:-}\n VAPI_ASSISTANT_ID: ${VAPI_ASSISTANT_ID:-}\n VAPI_WEBHOOK_SECRET: ${VAPI_WEBHOOK_SECRET:-}\n TWILIO_ACCOUNT_SID: ${TWILIO_ACCOUNT_SID:-}\n TWILIO_AUTH_TOKEN: ${TWILIO_AUTH_TOKEN:-}\n TWILIO_PHONE_NUMBER: ${TWILIO_PHONE_NUMBER:-+19492391845}\n ports:\n - \"0.0.0.0:${BETTY_OPERATOR_HOST_PORT:-18120}:8000\"\n volumes:\n - betty_operator_data:/app/data\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 15s\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n n8n:\n image: n8nio/n8n:latest\n container_name: betty-n8n\n depends_on:\n postgres:\n condition: service_healthy\n ollama:\n condition: service_healthy\n ollama-wrapper:\n condition: service_healthy\n betty-memory:\n condition: service_healthy\n betty-actions:\n condition: service_healthy\n betty-lookup:\n condition: service_healthy\n betty-operator:\n condition: service_healthy\n environment:\n N8N_HOST: ${N8N_HOST:-localhost}\n N8N_PORT: 5678\n N8N_PROTOCOL: ${N8N_PROTOCOL:-http}\n WEBHOOK_URL: ${WEBHOOK_URL:-http://localhost:15678/}\n GENERIC_TIMEZONE: ${GENERIC_TIMEZONE:-America/Los_Angeles}\n N8N_ENCRYPTION_KEY: ${N8N_ENCRYPTION_KEY:?set N8N_ENCRYPTION_KEY in .env}\n N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS: \"true\"\n N8N_DIAGNOSTICS_ENABLED: \"false\"\n N8N_PERSONALIZATION_ENABLED: \"false\"\n N8N_SECURE_COOKIE: \"false\"\n N8N_BASIC_AUTH_ACTIVE: ${N8N_BASIC_AUTH_ACTIVE:-true}\n N8N_BASIC_AUTH_USER: ${N8N_BASIC_AUTH_USER:?set N8N_BASIC_AUTH_USER in .env}\n N8N_BASIC_AUTH_PASSWORD: ${N8N_BASIC_AUTH_PASSWORD:?set N8N_BASIC_AUTH_PASSWORD in .env}\n DB_TYPE: postgresdb\n DB_POSTGRESDB_HOST: postgres\n DB_POSTGRESDB_PORT: 5432\n DB_POSTGRESDB_DATABASE: ${POSTGRES_DB:-n8n}\n DB_POSTGRESDB_USER: ${POSTGRES_USER:-n8n}\n DB_POSTGRESDB_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n OLLAMA_WRAPPER_URL: http://ollama-wrapper:8000\n BETTY_MEMORY_URL: http://betty-memory:8000\n BETTY_ACTIONS_URL: http://betty-actions:8000\n BETTY_LOOKUP_URL: http://betty-lookup:8000\n BETTY_OPERATOR_URL: http://betty-operator:8000\n ports:\n - \"127.0.0.1:${N8N_HOST_PORT:-15678}:5678\"\n volumes:\n - n8n_data:/home/node/.n8n\n restart: unless-stopped\n security_opt:\n - no-new-privileges:true\n networks:\n - betty-net\n\nvolumes:\n postgres_data:\n chroma_data:\n ollama_data:\n betty_operator_data:\n n8n_data:\n\nnetworks:\n betty-net:\n driver: bridge\n"}, "4ca93b88ab8a4ac1a7b18aecb09c97d784c7f5b4": {"t": 1785769602.9500952, "n": 6032, "h": "f4262cc1b6d7851d", "c": "name: betty-stack\n\nservices:\n postgres:\n image: postgres:16-alpine\n container_name: betty-postgres\n environment:\n POSTGRES_USER: ${POSTGRES_USER:-n8n}\n POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n POSTGRES_DB: ${POSTGRES_DB:-n8n}\n volumes:\n - postgres_data:/var/lib/postgresql/data\n healthcheck:\n test: [\"CMD-SHELL\", \"pg_isready -U ${POSTGRES_USER:-n8n} -d ${POSTGRES_DB:-n8n}\"]\n interval: 10s\n timeout: 5s\n retries: 10\n start_period: 15s\n restart: unless-stopped\n security_opt:\n - no-new-privileges:true\n networks:\n - betty-net\n\n chroma:\n image: chromadb/chroma:0.5.23\n container_name: betty-chroma\n volumes:\n - chroma_data:/chroma/chroma\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/api/v1/heartbeat', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 15s\n restart: unless-stopped\n security_opt:\n - no-new-privileges:true\n networks:\n - betty-net\n\n ollama:\n image: ollama/ollama:latest\n container_name: betty-ollama\n ports:\n - \"127.0.0.1:${OLLAMA_HOST_PORT:-11435}:11434\"\n volumes:\n - ollama_data:/root/.ollama\n healthcheck:\n test: [\"CMD-SHELL\", \"ollama list >/dev/null 2>&1 || exit 1\"]\n interval: 20s\n timeout: 10s\n retries: 6\n start_period: 30s\n restart: unless-stopped\n security_opt:\n - no-new-privileges:true\n networks:\n - betty-net\n\n ollama-wrapper:\n build:\n context: ./ollama-wrapper\n image: betty/ollama-wrapper:local\n container_name: betty-ollama-wrapper\n depends_on:\n ollama:\n condition: service_healthy\n environment:\n OLLAMA_URL: http://ollama:11434\n API_USERNAME: ${WRAPPER_API_USERNAME:?set WRAPPER_API_USERNAME in .env}\n API_PASSWORD: ${WRAPPER_API_PASSWORD:?set WRAPPER_API_PASSWORD in .env}\n API_BEARER_TOKEN: ${WRAPPER_API_BEARER_TOKEN:-}\n WRAPPER_REQUEST_TIMEOUT_SECONDS: ${WRAPPER_REQUEST_TIMEOUT_SECONDS:-120}\n LOG_LEVEL: ${WRAPPER_LOG_LEVEL:-info}\n ports:\n - \"127.0.0.1:${WRAPPER_HOST_PORT:-18000}:8000\"\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 10s\n read_only: true\n tmpfs:\n - /tmp\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n betty-memory:\n build:\n context: ./services/memory\n image: betty/memory-service:local\n container_name: betty-memory\n depends_on:\n postgres:\n condition: service_healthy\n chroma:\n condition: service_healthy\n ollama:\n condition: service_healthy\n environment:\n BETTY_MEMORY_POSTGRES_HOST: postgres\n BETTY_MEMORY_POSTGRES_PORT: 5432\n BETTY_MEMORY_POSTGRES_DB: ${POSTGRES_DB:-n8n}\n BETTY_MEMORY_POSTGRES_USER: ${POSTGRES_USER:-n8n}\n BETTY_MEMORY_POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n BETTY_MEMORY_CHROMA_HOST: chroma\n BETTY_MEMORY_CHROMA_PORT: 8000\n BETTY_MEMORY_COLLECTION: ${BETTY_MEMORY_COLLECTION:-betty_memory}\n BETTY_MEMORY_OLLAMA_URL: http://ollama:11434\n BETTY_MEMORY_EMBED_MODEL: ${BETTY_MEMORY_EMBED_MODEL:-nomic-embed-text}\n BETTY_MEMORY_SEMANTIC_DISTANCE_THRESHOLD: ${BETTY_MEMORY_SEMANTIC_DISTANCE_THRESHOLD:-0.35}\n BETTY_MEMORY_LOG_LEVEL: ${BETTY_MEMORY_LOG_LEVEL:-info}\n BETTY_MEMORY_DEFAULT_TIMEZONE: ${GENERIC_TIMEZONE:-America/Los_Angeles}\n ports:\n - \"127.0.0.1:${BETTY_MEMORY_HOST_PORT:-18100}:8000\"\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 15s\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n n8n:\n image: n8nio/n8n:latest\n container_name: betty-n8n\n depends_on:\n postgres:\n condition: service_healthy\n ollama:\n condition: service_healthy\n ollama-wrapper:\n condition: service_healthy\n betty-memory:\n condition: service_healthy\n environment:\n N8N_HOST: ${N8N_HOST:-localhost}\n N8N_PORT: 5678\n N8N_PROTOCOL: ${N8N_PROTOCOL:-http}\n WEBHOOK_URL: ${WEBHOOK_URL:-http://localhost:15678/}\n GENERIC_TIMEZONE: ${GENERIC_TIMEZONE:-America/Los_Angeles}\n N8N_ENCRYPTION_KEY: ${N8N_ENCRYPTION_KEY:?set N8N_ENCRYPTION_KEY in .env}\n N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS: \"true\"\n N8N_DIAGNOSTICS_ENABLED: \"false\"\n N8N_PERSONALIZATION_ENABLED: \"false\"\n N8N_SECURE_COOKIE: \"false\"\n N8N_BASIC_AUTH_ACTIVE: ${N8N_BASIC_AUTH_ACTIVE:-true}\n N8N_BASIC_AUTH_USER: ${N8N_BASIC_AUTH_USER:?set N8N_BASIC_AUTH_USER in .env}\n N8N_BASIC_AUTH_PASSWORD: ${N8N_BASIC_AUTH_PASSWORD:?set N8N_BASIC_AUTH_PASSWORD in .env}\n DB_TYPE: postgresdb\n DB_POSTGRESDB_HOST: postgres\n DB_POSTGRESDB_PORT: 5432\n DB_POSTGRESDB_DATABASE: ${POSTGRES_DB:-n8n}\n DB_POSTGRESDB_USER: ${POSTGRES_USER:-n8n}\n DB_POSTGRESDB_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n OLLAMA_WRAPPER_URL: http://ollama-wrapper:8000\n BETTY_MEMORY_URL: http://betty-memory:8000\n ports:\n - \"127.0.0.1:${N8N_HOST_PORT:-15678}:5678\"\n volumes:\n - n8n_data:/home/node/.n8n\n restart: unless-stopped\n security_opt:\n - no-new-privileges:true\n networks:\n - betty-net\n\nvolumes:\n postgres_data:\n chroma_data:\n ollama_data:\n n8n_data:\n\nnetworks:\n betty-net:\n driver: bridge\n"}, "1efc591a22219ba6fa309398394275336d18ebc8": {"t": 1785769604.3385262, "n": 3801, "h": "72431acf15dd46d5", "c": "name: betty-stack\n\nservices:\n postgres:\n image: postgres:16-alpine\n container_name: betty-postgres\n environment:\n POSTGRES_USER: ${POSTGRES_USER:-n8n}\n POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n POSTGRES_DB: ${POSTGRES_DB:-n8n}\n volumes:\n - postgres_data:/var/lib/postgresql/data\n healthcheck:\n test: [\"CMD-SHELL\", \"pg_isready -U ${POSTGRES_USER:-n8n} -d ${POSTGRES_DB:-n8n}\"]\n interval: 10s\n timeout: 5s\n retries: 10\n start_period: 15s\n restart: unless-stopped\n security_opt:\n - no-new-privileges:true\n networks:\n - betty-net\n\n ollama:\n image: ollama/ollama:latest\n container_name: betty-ollama\n ports:\n - \"127.0.0.1:${OLLAMA_HOST_PORT:-11435}:11434\"\n volumes:\n - ollama_data:/root/.ollama\n healthcheck:\n test: [\"CMD-SHELL\", \"ollama list >/dev/null 2>&1 || exit 1\"]\n interval: 20s\n timeout: 10s\n retries: 6\n start_period: 30s\n restart: unless-stopped\n security_opt:\n - no-new-privileges:true\n networks:\n - betty-net\n\n ollama-wrapper:\n build:\n context: ./ollama-wrapper\n image: betty/ollama-wrapper:local\n container_name: betty-ollama-wrapper\n depends_on:\n ollama:\n condition: service_healthy\n environment:\n OLLAMA_URL: http://ollama:11434\n API_USERNAME: ${WRAPPER_API_USERNAME:?set WRAPPER_API_USERNAME in .env}\n API_PASSWORD: ${WRAPPER_API_PASSWORD:?set WRAPPER_API_PASSWORD in .env}\n API_BEARER_TOKEN: ${WRAPPER_API_BEARER_TOKEN:-}\n WRAPPER_REQUEST_TIMEOUT_SECONDS: ${WRAPPER_REQUEST_TIMEOUT_SECONDS:-120}\n LOG_LEVEL: ${WRAPPER_LOG_LEVEL:-info}\n ports:\n - \"127.0.0.1:${WRAPPER_HOST_PORT:-18000}:8000\"\n healthcheck:\n test:\n [\n \"CMD\",\n \"python\",\n \"-c\",\n \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=3).read()\",\n ]\n interval: 15s\n timeout: 5s\n retries: 8\n start_period: 10s\n read_only: true\n tmpfs:\n - /tmp\n security_opt:\n - no-new-privileges:true\n restart: unless-stopped\n networks:\n - betty-net\n\n n8n:\n image: n8nio/n8n:latest\n container_name: betty-n8n\n depends_on:\n postgres:\n condition: service_healthy\n ollama:\n condition: service_healthy\n ollama-wrapper:\n condition: service_healthy\n environment:\n N8N_HOST: ${N8N_HOST:-localhost}\n N8N_PORT: 5678\n N8N_PROTOCOL: ${N8N_PROTOCOL:-http}\n WEBHOOK_URL: ${WEBHOOK_URL:-http://localhost:15678/}\n GENERIC_TIMEZONE: ${GENERIC_TIMEZONE:-America/Los_Angeles}\n N8N_ENCRYPTION_KEY: ${N8N_ENCRYPTION_KEY:?set N8N_ENCRYPTION_KEY in .env}\n N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS: \"true\"\n N8N_DIAGNOSTICS_ENABLED: \"false\"\n N8N_PERSONALIZATION_ENABLED: \"false\"\n N8N_SECURE_COOKIE: \"false\"\n N8N_BASIC_AUTH_ACTIVE: ${N8N_BASIC_AUTH_ACTIVE:-true}\n N8N_BASIC_AUTH_USER: ${N8N_BASIC_AUTH_USER:?set N8N_BASIC_AUTH_USER in .env}\n N8N_BASIC_AUTH_PASSWORD: ${N8N_BASIC_AUTH_PASSWORD:?set N8N_BASIC_AUTH_PASSWORD in .env}\n DB_TYPE: postgresdb\n DB_POSTGRESDB_HOST: postgres\n DB_POSTGRESDB_PORT: 5432\n DB_POSTGRESDB_DATABASE: ${POSTGRES_DB:-n8n}\n DB_POSTGRESDB_USER: ${POSTGRES_USER:-n8n}\n DB_POSTGRESDB_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}\n OLLAMA_WRAPPER_URL: http://ollama-wrapper:8000\n ports:\n - \"127.0.0.1:${N8N_HOST_PORT:-15678}:5678\"\n volumes:\n - n8n_data:/home/node/.n8n\n restart: unless-stopped\n security_opt:\n - no-new-privileges:true\n networks:\n - betty-net\n\nvolumes:\n postgres_data:\n ollama_data:\n n8n_data:\n\nnetworks:\n betty-net:\n driver: bridge\n"}}, "LEAN/Betty/betty-n8n/.env.example": {"c752ce706cf1702f80774980639568e0ce607a36": {"t": 1785747143.2407775, "n": 3520, "h": "3ae9b649cfa74026", "c": "# Copy this file to .env and replace placeholders (or run ./scripts/generate-secrets.sh)\n\n# Core timezone + public URL settings\nGENERIC_TIMEZONE=America/Los_Angeles\nN8N_HOST=localhost\nN8N_PROTOCOL=http\nN8N_HOST_PORT=15678\nOLLAMA_HOST_PORT=11435\nWRAPPER_HOST_PORT=18000\nWEBHOOK_URL=http://localhost:15678/\n\n# Postgres\nPOSTGRES_USER=n8n\nPOSTGRES_DB=n8n\nPOSTGRES_PASSWORD=replace_with_strong_password\n\n# n8n security\nN8N_ENCRYPTION_KEY=replace_with_32plus_char_key\nN8N_BASIC_AUTH_ACTIVE=true\nN8N_BASIC_AUTH_USER=betty_admin\nN8N_BASIC_AUTH_PASSWORD=replace_with_strong_password\n\n# Ollama wrapper auth\nWRAPPER_API_USERNAME=betty\nWRAPPER_API_PASSWORD=replace_with_strong_password\n# Optional: enable Bearer token auth for clients that cannot send HTTP Basic\nWRAPPER_API_BEARER_TOKEN=\nWRAPPER_REQUEST_TIMEOUT_SECONDS=120\nWRAPPER_LOG_LEVEL=info\nBETTY_MEMORY_HOST_PORT=18100\nBETTY_MEMORY_COLLECTION=betty-v1-facts\nBETTY_MEMORY_EMBED_MODEL=nomic-embed-text\nBETTY_MEMORY_SEMANTIC_DISTANCE_THRESHOLD=0.45\nBETTY_MEMORY_LOG_LEVEL=info\nBETTY_ACTIONS_HOST_PORT=18110\nBETTY_OPERATOR_HOST_PORT=18120\nBETTY_LOOKUP_HOST_PORT=18130\nBETTY_OPERATOR_STT_PROVIDER=whisper_local\nBETTY_OPERATOR_WHISPER_MODEL=base.en\nBETTY_OPERATOR_WHISPER_DEVICE=cpu\nBETTY_OPERATOR_WHISPER_COMPUTE_TYPE=int8\nBETTY_OPERATOR_WHISPER_DATA_DIR=/app/data/whisper\nBETTY_MODEL_MODE=default\nBETTY_DEFAULT_CHAT_PROVIDER=auto\nBETTY_DEFAULT_CHAT_MODEL=deepseek-chat\nBETTY_DEFAULT_LOCAL_CHAT_MODEL=llama3.1:8b\nBETTY_MODEL_DOLPHIN=dolphin-llama3:70b\nBETTY_MODEL_MEDICAL=medllama2\nBETTY_MODEL_MUSIC=llamusic/llamusic\nBETTY_MODEL_IMAGE=llava:34b\nBETTY_MODEL_WRITE=HammerAI/mythomax-l2\nBETTY_MODEL_CODE=qwen2.5-coder:32b\nBETTY_MODEL_RESEARCH=nemotron\nBETTY_MODEL_FAST=gemma2:27b\nKOKORO_ENABLED=true\nKOKORO_DEFAULT_VOICE=af_bella\nBETTY_OPERATOR_KOKORO_SPEED=1.0\nBETTY_OPERATOR_KOKORO_DATA_DIR=/app/data/kokoro\n# Set to your Tailscale Funnel HTTPS URL once wired (e.g. https://ai26.tail1234.ts.net)\nBETTY_PUBLIC_BASE_URL=https://YOUR_TAILSCALE_FUNNEL_URL\nGOOGLE_CALENDAR_OAUTH_REDIRECT_URI=\nBETTY_OWNER_PHONE=\nBETTY_OWNER_NAME=Vincent\nBETTY_HOME_CITY=Corona Del Mar, CA\nBETTY_OPERATOR_RATE_LIMIT_WINDOW_SECONDS=60\nBETTY_OPERATOR_ELEVENLABS_RATE_LIMIT=20\nBETTY_OPERATOR_TWILIO_RATE_LIMIT=20\nBETTY_OPERATOR_PIPER_DATA_DIR=/app/data/piper\nELEVENLABS_ENABLED=false\nTWILIO_ENABLED=false\nVAPI_ENABLED=false\nBRAVE_ENABLED=false\nSEARXNG_ENABLED=true\nSEARXNG_BASE_URL=http://host.docker.internal:8080\nBETTY_COST_DAILY_CAP_USD=5\nBETTY_COST_MONTHLY_CAP_USD=50\nBETTY_COST_WARNING_RATIO=0.8\nBETTY_ELEVENLABS_COST_PER_1K_CHARS_USD=0.30\nBETTY_TWILIO_SMS_COST_USD=0.008\nBETTY_VAPI_COST_PER_MINUTE_USD=0.075\nBETTY_DEEPSEEK_CHAT_INPUT_COST_PER_1M_USD=0.27\nBETTY_DEEPSEEK_CHAT_OUTPUT_COST_PER_1M_USD=1.10\nBETTY_DEEPSEEK_REASONER_INPUT_COST_PER_1M_USD=0.55\nBETTY_DEEPSEEK_REASONER_OUTPUT_COST_PER_1M_USD=2.19\n\n# Optional external integrations (fill when needed)\nELEVENLABS_API_KEY=\nELEVENLABS_VOICE_ID=4tRn1lSkEn13EVTuqb0g\nELEVENLABS_MODEL_ID=eleven_turbo_v2_5\nBRAVE_SEARCH_API_KEY=\nBETTY_LOOKUP_MODEL=\nVAPI_API_KEY=\nVAPI_ASSISTANT_ID=\nVAPI_WEBHOOK_SECRET=replace_with_vapi_webhook_secret\nTWILIO_ACCOUNT_SID=\nTWILIO_AUTH_TOKEN=\n# Betty dedicated Twilio number in E.164 format.\nTWILIO_PHONE_NUMBER=+19492391845\nPIPER_VOICE_MODEL=en_US-lessac-medium\nNGROK_AUTHTOKEN=\nGOOGLE_CALENDAR_CLIENT_ID=\nGOOGLE_CALENDAR_CLIENT_SECRET=\nDEEPSEEK_API_KEY=\nDEEPSEEK_BASE_URL=https://api.deepseek.com\nBETTY_DEEPSEEK_MODEL=deepseek-chat\nBETTY_DEEPSEEK_REASONER_MODEL=deepseek-reasoner\nBETTY_DEEPSEEK_TIMEOUT_SECONDS=120\n"}, "e0496557da5335b6aa70e7720fc059c60980d89b": {"t": 1785769585.7764528, "n": 3520, "h": "3ae9b649cfa74026", "c": "# Copy this file to .env and replace placeholders (or run ./scripts/generate-secrets.sh)\n\n# Core timezone + public URL settings\nGENERIC_TIMEZONE=America/Los_Angeles\nN8N_HOST=localhost\nN8N_PROTOCOL=http\nN8N_HOST_PORT=15678\nOLLAMA_HOST_PORT=11435\nWRAPPER_HOST_PORT=18000\nWEBHOOK_URL=http://localhost:15678/\n\n# Postgres\nPOSTGRES_USER=n8n\nPOSTGRES_DB=n8n\nPOSTGRES_PASSWORD=replace_with_strong_password\n\n# n8n security\nN8N_ENCRYPTION_KEY=replace_with_32plus_char_key\nN8N_BASIC_AUTH_ACTIVE=true\nN8N_BASIC_AUTH_USER=betty_admin\nN8N_BASIC_AUTH_PASSWORD=replace_with_strong_password\n\n# Ollama wrapper auth\nWRAPPER_API_USERNAME=betty\nWRAPPER_API_PASSWORD=replace_with_strong_password\n# Optional: enable Bearer token auth for clients that cannot send HTTP Basic\nWRAPPER_API_BEARER_TOKEN=\nWRAPPER_REQUEST_TIMEOUT_SECONDS=120\nWRAPPER_LOG_LEVEL=info\nBETTY_MEMORY_HOST_PORT=18100\nBETTY_MEMORY_COLLECTION=betty-v1-facts\nBETTY_MEMORY_EMBED_MODEL=nomic-embed-text\nBETTY_MEMORY_SEMANTIC_DISTANCE_THRESHOLD=0.45\nBETTY_MEMORY_LOG_LEVEL=info\nBETTY_ACTIONS_HOST_PORT=18110\nBETTY_OPERATOR_HOST_PORT=18120\nBETTY_LOOKUP_HOST_PORT=18130\nBETTY_OPERATOR_STT_PROVIDER=whisper_local\nBETTY_OPERATOR_WHISPER_MODEL=base.en\nBETTY_OPERATOR_WHISPER_DEVICE=cpu\nBETTY_OPERATOR_WHISPER_COMPUTE_TYPE=int8\nBETTY_OPERATOR_WHISPER_DATA_DIR=/app/data/whisper\nBETTY_MODEL_MODE=default\nBETTY_DEFAULT_CHAT_PROVIDER=auto\nBETTY_DEFAULT_CHAT_MODEL=deepseek-chat\nBETTY_DEFAULT_LOCAL_CHAT_MODEL=llama3.1:8b\nBETTY_MODEL_DOLPHIN=dolphin-llama3:70b\nBETTY_MODEL_MEDICAL=medllama2\nBETTY_MODEL_MUSIC=llamusic/llamusic\nBETTY_MODEL_IMAGE=llava:34b\nBETTY_MODEL_WRITE=HammerAI/mythomax-l2\nBETTY_MODEL_CODE=qwen2.5-coder:32b\nBETTY_MODEL_RESEARCH=nemotron\nBETTY_MODEL_FAST=gemma2:27b\nKOKORO_ENABLED=true\nKOKORO_DEFAULT_VOICE=af_bella\nBETTY_OPERATOR_KOKORO_SPEED=1.0\nBETTY_OPERATOR_KOKORO_DATA_DIR=/app/data/kokoro\n# Set to your Tailscale Funnel HTTPS URL once wired (e.g. https://ai26.tail1234.ts.net)\nBETTY_PUBLIC_BASE_URL=https://YOUR_TAILSCALE_FUNNEL_URL\nGOOGLE_CALENDAR_OAUTH_REDIRECT_URI=\nBETTY_OWNER_PHONE=\nBETTY_OWNER_NAME=Vincent\nBETTY_HOME_CITY=Corona Del Mar, CA\nBETTY_OPERATOR_RATE_LIMIT_WINDOW_SECONDS=60\nBETTY_OPERATOR_ELEVENLABS_RATE_LIMIT=20\nBETTY_OPERATOR_TWILIO_RATE_LIMIT=20\nBETTY_OPERATOR_PIPER_DATA_DIR=/app/data/piper\nELEVENLABS_ENABLED=false\nTWILIO_ENABLED=false\nVAPI_ENABLED=false\nBRAVE_ENABLED=false\nSEARXNG_ENABLED=true\nSEARXNG_BASE_URL=http://host.docker.internal:8080\nBETTY_COST_DAILY_CAP_USD=5\nBETTY_COST_MONTHLY_CAP_USD=50\nBETTY_COST_WARNING_RATIO=0.8\nBETTY_ELEVENLABS_COST_PER_1K_CHARS_USD=0.30\nBETTY_TWILIO_SMS_COST_USD=0.008\nBETTY_VAPI_COST_PER_MINUTE_USD=0.075\nBETTY_DEEPSEEK_CHAT_INPUT_COST_PER_1M_USD=0.27\nBETTY_DEEPSEEK_CHAT_OUTPUT_COST_PER_1M_USD=1.10\nBETTY_DEEPSEEK_REASONER_INPUT_COST_PER_1M_USD=0.55\nBETTY_DEEPSEEK_REASONER_OUTPUT_COST_PER_1M_USD=2.19\n\n# Optional external integrations (fill when needed)\nELEVENLABS_API_KEY=\nELEVENLABS_VOICE_ID=4tRn1lSkEn13EVTuqb0g\nELEVENLABS_MODEL_ID=eleven_turbo_v2_5\nBRAVE_SEARCH_API_KEY=\nBETTY_LOOKUP_MODEL=\nVAPI_API_KEY=\nVAPI_ASSISTANT_ID=\nVAPI_WEBHOOK_SECRET=replace_with_vapi_webhook_secret\nTWILIO_ACCOUNT_SID=\nTWILIO_AUTH_TOKEN=\n# Betty dedicated Twilio number in E.164 format.\nTWILIO_PHONE_NUMBER=+19492391845\nPIPER_VOICE_MODEL=en_US-lessac-medium\nNGROK_AUTHTOKEN=\nGOOGLE_CALENDAR_CLIENT_ID=\nGOOGLE_CALENDAR_CLIENT_SECRET=\nDEEPSEEK_API_KEY=\nDEEPSEEK_BASE_URL=https://api.deepseek.com\nBETTY_DEEPSEEK_MODEL=deepseek-chat\nBETTY_DEEPSEEK_REASONER_MODEL=deepseek-reasoner\nBETTY_DEEPSEEK_TIMEOUT_SECONDS=120\n"}, "4ca93b88ab8a4ac1a7b18aecb09c97d784c7f5b4": {"t": 1785769588.903257, "n": 1144, "h": "99ef82342e6b55a9", "c": "# Copy this file to .env and replace placeholders (or run ./scripts/generate-secrets.sh)\n\n# Core timezone + public URL settings\nGENERIC_TIMEZONE=America/Los_Angeles\nN8N_HOST=localhost\nN8N_PROTOCOL=http\nN8N_HOST_PORT=15678\nOLLAMA_HOST_PORT=11435\nWRAPPER_HOST_PORT=18000\nWEBHOOK_URL=http://localhost:15678/\n\n# Postgres\nPOSTGRES_USER=n8n\nPOSTGRES_DB=n8n\nPOSTGRES_PASSWORD=replace_with_strong_password\n\n# n8n security\nN8N_ENCRYPTION_KEY=replace_with_32plus_char_key\nN8N_BASIC_AUTH_ACTIVE=true\nN8N_BASIC_AUTH_USER=betty_admin\nN8N_BASIC_AUTH_PASSWORD=replace_with_strong_password\n\n# Ollama wrapper auth\nWRAPPER_API_USERNAME=betty\nWRAPPER_API_PASSWORD=replace_with_strong_password\n# Optional: enable Bearer token auth for clients that cannot send HTTP Basic\nWRAPPER_API_BEARER_TOKEN=\nWRAPPER_REQUEST_TIMEOUT_SECONDS=120\nWRAPPER_LOG_LEVEL=info\nBETTY_MEMORY_HOST_PORT=18100\nBETTY_MEMORY_COLLECTION=betty_memory\nBETTY_MEMORY_EMBED_MODEL=nomic-embed-text\nBETTY_MEMORY_SEMANTIC_DISTANCE_THRESHOLD=0.35\nBETTY_MEMORY_LOG_LEVEL=info\n\n# Optional external integrations (fill when needed)\nELEVENLABS_API_KEY=\nBRAVE_SEARCH_API_KEY=\nVAPI_API_KEY=\nNGROK_AUTHTOKEN=\n"}, "1efc591a22219ba6fa309398394275336d18ebc8": {"t": 1785769590.2812586, "n": 962, "h": "6cea4b310e5eb823", "c": "# Copy this file to .env and replace placeholders (or run ./scripts/generate-secrets.sh)\n\n# Core timezone + public URL settings\nGENERIC_TIMEZONE=America/Los_Angeles\nN8N_HOST=localhost\nN8N_PROTOCOL=http\nN8N_HOST_PORT=15678\nOLLAMA_HOST_PORT=11435\nWRAPPER_HOST_PORT=18000\nWEBHOOK_URL=http://localhost:15678/\n\n# Postgres\nPOSTGRES_USER=n8n\nPOSTGRES_DB=n8n\nPOSTGRES_PASSWORD=replace_with_strong_password\n\n# n8n security\nN8N_ENCRYPTION_KEY=replace_with_32plus_char_key\nN8N_BASIC_AUTH_ACTIVE=true\nN8N_BASIC_AUTH_USER=betty_admin\nN8N_BASIC_AUTH_PASSWORD=replace_with_strong_password\n\n# Ollama wrapper auth\nWRAPPER_API_USERNAME=betty\nWRAPPER_API_PASSWORD=replace_with_strong_password\n# Optional: enable Bearer token auth for clients that cannot send HTTP Basic\nWRAPPER_API_BEARER_TOKEN=\nWRAPPER_REQUEST_TIMEOUT_SECONDS=120\nWRAPPER_LOG_LEVEL=info\n\n# Optional external integrations (fill when needed)\nELEVENLABS_API_KEY=\nBRAVE_SEARCH_API_KEY=\nVAPI_API_KEY=\nNGROK_AUTHTOKEN=\n"}}, "LEAN/Betty/betty-n8n/docs/BETTY_TEST_LOG.md": {"c752ce706cf1702f80774980639568e0ce607a36": {"t": 1786068451.9834876, "n": 31054, "h": "e0bc473ed9e669c5", "c": "# Betty Test Log\n\n## Purpose\n\nThis file is the append-only Betty test record.\n\nEvery completed deliverable should append:\n\n- date\n- deliverable name\n- commands run\n- artifacts\n- pass/fail\n- blockers\n\n## Existing Baseline Evidence\n\n### 2026-03-14 - Betty Stack Baseline\n\nSource references:\n\n- [PROJECTS.md](../PROJECTS.md)\n- `artifacts/calendar_reminders.json`\n- `artifacts/calendar_test_sprint2.json`\n- wrapper and db Gate D artifacts\n\nRecorded status:\n\n- Gate A: PASS\n- Gate B: PASS\n- Gate C: PASS\n- Gate D: PASS\n\nMeaning:\n\n- stack booted\n- model was available\n- wrapper auth worked\n- n8n calendar workflow completed end-to-end\n\n## Template\n\n### YYYY-MM-DD - Deliverable Name\n\n- Commands run:\n- Files changed:\n- Artifacts:\n- Result: PASS or FAIL\n- Notes:\n\n### 2026-03-16 - Betty Memory Core Baseline\n\n- Commands run:\n - `./scripts/sync-postgres-role-password.sh`\n - `./scripts/test-memory-core.sh`\n- Files changed:\n - `docker-compose.yml`\n - `.env.example`\n - `README.md`\n - `services/memory/Dockerfile`\n - `services/memory/requirements.txt`\n - `services/memory/main.py`\n - `scripts/sync-postgres-role-password.sh`\n - `scripts/test-memory-core.sh`\n - `docs/BETTY_STATE.md`\n - `docs/BETTY_NEXT.md`\n - `docs/BETTY_TEST_LOG.md`\n- Artifacts:\n - `artifacts/postgres_password_sync.txt`\n - `artifacts/memory_health.json`\n - `artifacts/memory_reset_response.json`\n - `artifacts/memory_ingest_remember.json`\n - `artifacts/memory_recall_exact.json`\n - `artifacts/memory_recall_semantic.json`\n - `artifacts/memory_ingest_update.json`\n - `artifacts/memory_recall_updated.json`\n - `artifacts/memory_list_pinned.json`\n - `artifacts/memory_ingest_delete.json`\n - `artifacts/memory_recall_deleted.json`\n - `artifacts/memory_docker_ps.txt`\n - `artifacts/memory_db_count.txt`\n - `artifacts/memory_acceptance_check.txt`\n- Result: PASS\n- Notes:\n - exact recall returns literal stored text, semantic recall uses the required prefix, and not-found uses the approved Betty wording\n - update and delete are both verified from fresh artifacts\n - Postgres role password drift was present in the live environment and is now recoverable via `scripts/sync-postgres-role-password.sh`\n - smoke cleanup is fixture-clean and leaves the `facts` table empty at the end of the run\n\n### 2026-03-16 - Betty Action Approval Baseline\n\n- Commands run:\n - `./scripts/sync-postgres-role-password.sh`\n - `./scripts/test-action-approval.sh`\n- Files changed:\n - `docker-compose.yml`\n - `.env.example`\n - `README.md`\n - `services/actions/Dockerfile`\n - `services/actions/requirements.txt`\n - `services/actions/main.py`\n - `scripts/test-action-approval.sh`\n - `workflows/n8n/betty_action_approval_seed.json`\n - `docs/BETTY_STATE.md`\n - `docs/BETTY_NEXT.md`\n - `docs/BETTY_TEST_LOG.md`\n- Artifacts:\n - `artifacts/action_postgres_password_sync.txt`\n - `artifacts/action_health.json`\n - `artifacts/action_reset_start.json`\n - `artifacts/action_draft_yes.json`\n - `artifacts/action_confirm_yes.json`\n - `artifacts/action_draft_no.json`\n - `artifacts/action_reject_no.json`\n - `artifacts/action_draft_timeout.json`\n - `artifacts/action_timeout_sweep.json`\n - `artifacts/action_non_risky_query.json`\n - `artifacts/action_draft_invalid.json`\n - `artifacts/action_invalid_first.json`\n - `artifacts/action_invalid_second.json`\n - `artifacts/action_docker_ps.txt`\n - `artifacts/action_pending_status_counts.txt`\n - `artifacts/action_audit_counts.txt`\n - `artifacts/action_calendar_event_count.txt`\n - `artifacts/action_acceptance_check.txt`\n - `artifacts/action_reset_final.json`\n - `artifacts/action_post_reset_pending_count.txt`\n- Result: PASS\n- Notes:\n - risky calendar creation now requires confirmation with the approved prompt and safety-first tone\n - yes executes, no aborts, timeout aborts, non-risky queries bypass prompts, and invalid responses re-prompt once then time out\n - audit evidence is stored in `action_audit`, pending state is stored in `approval_pending`, and the local confirmed write is stored in `calendar_events`\n - smoke cleanup is fixture-clean and leaves pending approval count `0` at the end of the run\n\n### 2026-03-16 - Betty Voice and SMS Operator Baseline\n\n- Commands run:\n - `./scripts/sync-postgres-role-password.sh`\n - `./scripts/test-voice-sms-operator.sh`\n- Files changed:\n - `docker-compose.yml`\n - `.env.example`\n - `README.md`\n - `services/actions/main.py`\n - `services/operator/Dockerfile`\n - `services/operator/requirements.txt`\n - `services/operator/main.py`\n - `scripts/test-voice-sms-operator.sh`\n - `config/vapi/betty_vapi_assistant.stub.json`\n - `config/twilio/betty_twilio_webhook.stub.json`\n - `docs/BETTY_STATE.md`\n - `docs/BETTY_NEXT.md`\n - `docs/BETTY_TEST_LOG.md`\n- Artifacts:\n - `artifacts/operator_postgres_password_sync.txt`\n - `artifacts/operator_memory_health.json`\n - `artifacts/operator_actions_health.json`\n - `artifacts/operator_health.json`\n - `artifacts/operator_reset_start.json`\n - `artifacts/operator_action_reset_start.json`\n - `artifacts/operator_memory_reset_start.json`\n - `artifacts/operator_seed_contact.json`\n - `artifacts/operator_memory_seed.json`\n - `artifacts/operator_voice_recall.json`\n - `artifacts/operator_voice_calendar_draft.json`\n - `artifacts/operator_voice_calendar_yes.json`\n - `artifacts/operator_voice_calendar_draft_no.json`\n - `artifacts/operator_voice_calendar_no.json`\n - `artifacts/operator_sms_draft.json`\n - `artifacts/operator_sms_yes.json`\n - `artifacts/operator_unknown_voice.json`\n - `artifacts/operator_counts_before_reset.json`\n - `artifacts/operator_action_pending_counts.txt`\n - `artifacts/operator_action_audit_counts.txt`\n - `artifacts/operator_action_calendar_event_count.txt`\n - `artifacts/operator_acceptance_check.txt`\n - `artifacts/operator_docker_ps.txt`\n - `artifacts/operator_reset_final.json`\n - `artifacts/operator_action_reset_final.json`\n - `artifacts/operator_memory_reset_final.json`\n - `artifacts/operator_post_reset_counts.json`\n- Result: PASS\n- Notes:\n - inbound voice can greet Vincent, speak exact saved facts, and complete yes / no approval handling\n - inbound SMS can create an approval request and complete it with `YES`\n - unknown callers are politely deflected without exposing personal data\n - the current provider integration is stubbed at the config layer; transport bridging is the next deliverable\n - smoke cleanup is fixture-clean and leaves operator interaction counts `0` at the end of the run\n\n### 2026-03-16 - Betty Provider Bridge Baseline\n\n- Commands run:\n - `python -m py_compile services/operator/main.py services/actions/main.py`\n - `docker compose config`\n - `./scripts/test-provider-bridge.sh`\n- Files changed:\n - `docker-compose.yml`\n - `.env.example`\n - `README.md`\n - `services/actions/main.py`\n - `services/operator/main.py`\n - `scripts/test-provider-bridge.sh`\n - `config/vapi/betty_vapi_assistant.stub.json`\n - `config/twilio/betty_twilio_webhook.stub.json`\n - `docs/BETTY_STATE.md`\n - `docs/BETTY_NEXT.md`\n - `docs/BETTY_TEST_LOG.md`\n- Artifacts:\n - `artifacts/provider_postgres_password_sync.txt`\n - `artifacts/provider_memory_health.json`\n - `artifacts/provider_actions_health.json`\n - `artifacts/provider_operator_health.json`\n - `artifacts/provider_vapi_assistant_request_payload.json`\n - `artifacts/provider_vapi_assistant_request.json`\n - `artifacts/provider_vapi_recall_payload.json`\n - `artifacts/provider_vapi_recall.json`\n - `artifacts/provider_vapi_calendar_draft_payload.json`\n - `artifacts/provider_vapi_calendar_draft.json`\n - `artifacts/provider_vapi_calendar_yes_payload.json`\n - `artifacts/provider_vapi_calendar_yes.json`\n - `artifacts/provider_twilio_draft.xml`\n - `artifacts/provider_twilio_yes.xml`\n - `artifacts/provider_counts_before_reset.json`\n - `artifacts/provider_action_pending_counts.txt`\n - `artifacts/provider_action_audit_counts.txt`\n - `artifacts/provider_action_calendar_event_count.txt`\n - `artifacts/provider_acceptance_check.txt`\n - `artifacts/provider_docker_ps.txt`\n - `artifacts/provider_reset_final.json`\n - `artifacts/provider_action_reset_final.json`\n - `artifacts/provider_memory_reset_final.json`\n - `artifacts/provider_post_reset_counts.json`\n- Result: PASS\n- Notes:\n - Vapi assistant-request and tool-call payloads now bridge into the local operator service\n - Twilio SMS form posts now bridge into the local operator service and return TwiML replies\n - Betty's dedicated Twilio number and ElevenLabs Serafina defaults are now encoded in the provider layer\n - smoke cleanup is fixture-clean and leaves provider bridge counts `0` at the end of the run\n\n### 2026-03-17 - Betty Provider Bridge Baseline (Hardened Webhooks)\n\n- Commands run:\n - `python -m py_compile services/operator/main.py services/actions/main.py`\n - `bash -n scripts/test-provider-bridge.sh`\n - `docker compose config`\n - `./scripts/test-provider-bridge.sh`\n- Files changed:\n - `services/operator/main.py`\n - `services/operator/Dockerfile`\n - `services/operator/requirements.txt`\n - `docker-compose.yml`\n - `.env.example`\n - `config/vapi/betty_vapi_assistant.stub.json`\n - `config/twilio/betty_twilio_webhook.stub.json`\n - `scripts/test-provider-bridge.sh`\n - `README.md`\n - `docs/BETTY_STATE.md`\n - `docs/BETTY_TEST_LOG.md`\n- Artifacts:\n - `artifacts/provider_postgres_password_sync.txt`\n - `artifacts/provider_memory_health.json`\n - `artifacts/provider_actions_health.json`\n - `artifacts/provider_operator_health.json`\n - `artifacts/provider_webhook_voice_assistant.json`\n - `artifacts/provider_webhook_voice_recall.json`\n - `artifacts/provider_tts_recall.json`\n - `artifacts/provider_tts_fallback.json`\n - `artifacts/provider_webhook_sms_draft.xml`\n - `artifacts/provider_webhook_sms_yes.xml`\n - `artifacts/provider_unknown_sms.xml`\n - `artifacts/provider_invalid_twilio_signature_status.txt`\n - `artifacts/provider_invalid_vapi_signature_status.txt`\n - `artifacts/provider_log_sanitization.txt`\n - `artifacts/provider_counts_before_reset.json`\n - `artifacts/provider_action_pending_counts.txt`\n - `artifacts/provider_action_audit_counts.txt`\n - `artifacts/provider_action_calendar_event_count.txt`\n - `artifacts/provider_acceptance_check.txt`\n - `artifacts/provider_docker_ps.txt`\n - `artifacts/provider_reset_final.json`\n - `artifacts/provider_action_reset_final.json`\n - `artifacts/provider_memory_reset_final.json`\n - `artifacts/provider_post_reset_counts.json`\n- Result: PASS\n- Notes:\n - provider traffic now uses signed `/webhook/voice` and `/webhook/sms` endpoints instead of the older unaudited callback shape\n - Twilio signatures are verified with `TWILIO_AUTH_TOKEN`, and Vapi requests are verified with `VAPI_WEBHOOK_SECRET`\n - provider logs now mask non-owner phone numbers and delete transcript/audio content after processing\n - ElevenLabs Serafina remains the primary TTS path, while Piper fallback is now fully exercised in the smoke test\n - live provider cutover still requires a real public HTTPS URL and console-side webhook registration\n\n### 2026-03-17 - Betty Live Voice And SMS Routing Hardening\n\n- Commands run:\n - `python -m py_compile services/operator/main.py services/memory/main.py services/actions/main.py`\n - `wsl -d Ubuntu -e bash -lc \"cd /mnt/c/Users/vince/Documents/DGX_Set_up/LEAN/Betty/betty-n8n && bash scripts/test-live-routing.sh\"`\n- Files changed:\n - `services/operator/main.py`\n - `services/memory/main.py`\n - `services/actions/main.py`\n - `.env.example`\n - `docker-compose.yml`\n - `scripts/test-live-routing.sh`\n - `docs/BETTY_STATE.md`\n - `docs/BETTY_TEST_LOG.md`\n- Artifacts:\n - `artifacts/routing_postgres_password_sync.txt`\n - `artifacts/routing_memory_health.json`\n - `artifacts/routing_actions_health.json`\n - `artifacts/routing_operator_health.json`\n - `artifacts/routing_voice_memory_save.json`\n - `artifacts/routing_voice_recall.json`\n - `artifacts/routing_voice_memory_update.json`\n - `artifacts/routing_voice_recall_updated.json`\n - `artifacts/routing_voice_memory_forget.json`\n - `artifacts/routing_voice_recall_missing.json`\n - `artifacts/routing_voice_action_draft.json`\n - `artifacts/routing_voice_action_yes.json`\n - `artifacts/routing_voice_action_draft_no.json`\n - `artifacts/routing_voice_action_no.json`\n - `artifacts/routing_sms_memory_save.xml`\n - `artifacts/routing_sms_memory_recall.xml`\n - `artifacts/routing_counts_before_reset.json`\n - `artifacts/routing_action_audit_counts.txt`\n - `artifacts/routing_calendar_event_count.txt`\n - `artifacts/routing_acceptance_check.txt`\n - `artifacts/routing_docker_ps.txt`\n - `artifacts/routing_reset_final.json`\n - `artifacts/routing_action_reset_final.json`\n - `artifacts/routing_memory_reset_final.json`\n - `artifacts/routing_memory_reset_sms_final.json`\n - `artifacts/routing_post_reset_counts.json`\n- Result: PASS\n- Notes:\n - live voice and SMS now route memory write/update/delete requests into the Betty memory service instead of relying on model improvisation\n - approval-handled reminder flows now return backend-authoritative confirmation and cancellation text without stale persona-prefix leakage\n - the memory and actions services now honor `BETTY_HOME_CITY` and `BETTY_OWNER_NAME`, keeping persona text consistent across operator, memory, and action responses\n - the routing smoke is fixture-clean at the tag level and proves the signed local `/webhook/voice` and `/webhook/sms` paths end to end\n\n### 2026-03-17 - Betty Grounded Lookup Baseline\n\n- Commands run:\n - `python -m py_compile services/operator/main.py services/memory/main.py services/actions/main.py services/lookup/main.py`\n - `docker compose config`\n - `docker compose up -d --build betty-lookup betty-operator`\n - `wsl -d Ubuntu -e bash -lc \"cd /mnt/c/Users/vince/Documents/DGX_Set_up/LEAN/Betty/betty-n8n && bash scripts/test-grounded-lookup.sh\"`\n- Files changed:\n - `services/lookup/Dockerfile`\n - `services/lookup/requirements.txt`\n - `services/lookup/main.py`\n - `services/operator/main.py`\n - `docker-compose.yml`\n - `.env.example`\n - `scripts/test-grounded-lookup.sh`\n - `docs/BETTY_STATE.md`\n - `docs/BETTY_TEST_LOG.md`\n - `docs/BETTY_NEXT.md`\n- Artifacts:\n - `artifacts/lookup_postgres_password_sync.txt`\n - `artifacts/lookup_memory_health.json`\n - `artifacts/lookup_actions_health.json`\n - `artifacts/lookup_lookup_health.json`\n - `artifacts/lookup_operator_health.json`\n - `artifacts/lookup_direct_general.json`\n - `artifacts/lookup_direct_weather.json`\n - `artifacts/lookup_operator_general.json`\n - `artifacts/lookup_operator_weather.json`\n - `artifacts/lookup_operator_memory_seed.json`\n - `artifacts/lookup_operator_memory.json`\n - `artifacts/lookup_operator_risky.json`\n - `artifacts/lookup_audit_rows.txt`\n - `artifacts/lookup_operator_rows.txt`\n - `artifacts/lookup_acceptance_check.txt`\n - `artifacts/lookup_docker_ps.txt`\n - `artifacts/lookup_reset_final.json`\n - `artifacts/lookup_action_reset_final.json`\n - `artifacts/lookup_memory_reset_final.json`\n - `artifacts/lookup_lookup_reset_final.json`\n- Result: PASS\n- Notes:\n - direct chat lookup and operator-routed voice lookup now return explicit sources\n - operator routing was tightened so generic factual/weather prompts do not get hijacked by semantic memory recall\n - memory recall still wins for personal saved facts, and risky actions still prompt for approval\n - the current Brave key appears to be on an Answers-oriented plan, so the lookup service now degrades honestly: it falls back from raw Brave web search to Brave Answers and attaches a provider source when per-result citations are unavailable\n - outbound Betty SMS remains blocked by Twilio compliance on the live `10DLC` number; that is an external carrier/compliance issue, not a local lookup/operator bug\n\n### 2026-03-18 - Betty Cost Guardrails Baseline\n\n- Commands run:\n - `python -m py_compile services/operator/main.py services/lookup/main.py services/memory/main.py services/actions/main.py`\n - `docker compose config`\n - `wsl -d Ubuntu -e bash -lc \"cd /mnt/c/Users/vince/Documents/DGX_Set_up/LEAN/Betty/betty-n8n && bash scripts/test-cost-guardrails.sh\"`\n- Files changed:\n - `services/operator/main.py`\n - `services/lookup/main.py`\n - `docker-compose.yml`\n - `.env.example`\n - `scripts/test-cost-guardrails.sh`\n - `docs/BETTY_STATE.md`\n - `docs/BETTY_TEST_LOG.md`\n - `docs/BETTY_NEXT.md`\n- Artifacts:\n - `artifacts/cost_guardrails_postgres_password_sync.txt`\n - `artifacts/cost_guardrails_operator_health_disabled.json`\n - `artifacts/cost_guardrails_lookup_health_disabled.json`\n - `artifacts/cost_guardrails_tts_disabled.json`\n - `artifacts/cost_guardrails_twilio_disabled.json`\n - `artifacts/cost_guardrails_vapi_disabled_status.txt`\n - `artifacts/cost_guardrails_brave_disabled_status.txt`\n - `artifacts/cost_guardrails_vapi_warning.json`\n - `artifacts/cost_guardrails_operator_health_warning.json`\n - `artifacts/cost_guardrails_usage_rows.txt`\n - `artifacts/cost_guardrails_acceptance_check.txt`\n - `artifacts/cost_guardrails_docker_ps.txt`\n - `artifacts/cost_guardrails_operator_reset_disabled_final.json`\n - `artifacts/cost_guardrails_lookup_reset_disabled_final.json`\n - `artifacts/cost_guardrails_operator_reset_warning_final.json`\n- Result: PASS\n- Notes:\n - provider flags now explicitly control ElevenLabs, Twilio, Vapi, and Brave behavior without breaking local fallbacks\n - ElevenLabs-disabled runs force Piper TTS, Vapi-disabled runs reject voice transport and force `whisper_local`, Twilio-disabled runs suppress outbound SMS, and Brave-disabled runs return a clear grounded-lookup unavailable response\n - provider usage is now logged to `provider_usage_events` with characters, minutes, estimated cost, and status so daily/monthly cap summaries can be computed locally\n - operator and lookup health endpoints now surface cost warning/block state; the acceptance smoke proves a daily warning threshold can trigger before a hard block\n - the long-term low-cost voice pivot remains a future option: `Google Voice + Asterisk Docker + Piper + Whisper`\n\n### 2026-03-18 - Betty Local Voice Pipeline Baseline\n\n- Commands run:\n - `python -m py_compile services/operator/main.py`\n - `bash scripts/test-local-voice-pipeline.sh`\n- Files changed:\n - `services/operator/Dockerfile`\n - `services/operator/requirements.txt`\n - `services/operator/main.py`\n - `docker-compose.yml`\n - `.env.example`\n - `scripts/test-local-voice-pipeline.sh`\n - `README.md`\n - `docs/BETTY_STATE.md`\n - `docs/BETTY_TEST_LOG.md`\n - `docs/BETTY_NEXT.md`\n- Artifacts:\n - `artifacts/local_voice_postgres_password_sync.txt`\n - `artifacts/local_voice_operator_health.json`\n - `artifacts/local_voice_voices.json`\n - `artifacts/local_voice_default_render.json`\n - `artifacts/local_voice_audition.json`\n - `artifacts/local_voice_piper_render.json`\n - `artifacts/local_voice_manifest.json`\n - `artifacts/local_voice_usage_rows.txt`\n - `artifacts/local_voice_acceptance_check.txt`\n - `artifacts/local_voice_docker_ps.txt`\n - `artifacts/local_voice_reset_final.json`\n - `artifacts/voice_auditions/betty_default_af_bella.wav`\n - `artifacts/voice_auditions/af_bella.wav`\n - `artifacts/voice_auditions/af_nicole.wav`\n - `artifacts/voice_auditions/af_heart.wav`\n - `artifacts/voice_auditions/bf_emma.wav`\n - `artifacts/voice_auditions/betty_piper_lessac.wav`\n- Result: PASS\n- Notes:\n - Kokoro is now Betty's local primary TTS path, with Piper preserved as the fallback path inside the same operator service\n - the operator image now installs the English SpaCy model required by Kokoro/Misaki, which was the only blocker preventing real local synthesis during the first smoke run\n - the acceptance smoke writes real local audition WAV files for four Kokoro candidates plus one Piper fallback sample so Vincent can choose Betty's voice without any cloud provider\n - provider usage counters now record Kokoro render events with zero estimated external cost, keeping the local-first cost story measurable\n\n### 2026-03-19 - Betty Mobile PWA Baseline\n\n- Commands run:\n - `python -m py_compile services/operator/main.py`\n - `bash scripts/test-mobile-pwa.sh`\n - `msedge.exe --headless --window-size=1440,960 --screenshot=artifacts/pwa_desktop.png http://127.0.0.1:18120/`\n - `msedge.exe --headless --window-size=430,932 --screenshot=artifacts/pwa_mobile.png http://127.0.0.1:18120/`\n- Files changed:\n - `services/operator/main.py`\n - `services/operator/Dockerfile`\n - `services/operator/static/index.html`\n - `services/operator/static/app.css`\n - `services/operator/static/app.js`\n - `services/operator/static/manifest.webmanifest`\n - `services/operator/static/service-worker.js`\n - `services/operator/static/icon.svg`\n - `scripts/test-mobile-pwa.sh`\n - `README.md`\n - `docs/BETTY_STATE.md`\n - `docs/BETTY_TEST_LOG.md`\n - `docs/BETTY_NEXT.md`\n- Artifacts:\n - `artifacts/pwa_postgres_password_sync.txt`\n - `artifacts/pwa_operator_health.json`\n - `artifacts/pwa_index.html`\n - `artifacts/pwa_manifest.json`\n - `artifacts/pwa_service_worker.js`\n - `artifacts/pwa_icon.svg`\n - `artifacts/pwa_chat_memory_seed.json`\n - `artifacts/pwa_chat_memory_recall.json`\n - `artifacts/pwa_chat_approval_prompt.json`\n - `artifacts/pwa_chat_approval_yes.json`\n - `artifacts/pwa_chat_approval_prompt_no.json`\n - `artifacts/pwa_chat_approval_no.json`\n - `artifacts/pwa_acceptance_check.txt`\n - `artifacts/pwa_desktop.png`\n - `artifacts/pwa_mobile.png`\n - `artifacts/pwa_docker_ps.txt`\n- Result: PASS\n- Notes:\n - Betty now exposes a text-first installable PWA from the operator service itself, avoiding a second frontend container and avoiding Vapi/Twilio for routine daily interaction\n - chat requests route into the same memory, lookup, and approval logic already used by voice and SMS, with session-based backend threading for follow-up yes/no approval responses\n - desktop and narrow mobile widths were rendered headlessly with Edge and saved under `artifacts/` as in-repo visual evidence\n - browser microphone capture is intentionally not in this baseline; the app is stable and installable now, and voice input is the next incremental step\n\n### 2026-03-19 - Betty Specialist Model Switcher Baseline\n\n- Commands run:\n - `python -m py_compile services/operator/main.py`\n - `docker compose config`\n - `bash scripts/test-model-switcher.sh`\n- Files changed:\n - `services/operator/main.py`\n - `docker-compose.yml`\n - `.env.example`\n - `scripts/test-model-switcher.sh`\n - `scripts/pull-specialist-models.sh`\n - `README.md`\n - `docs/BETTY_STATE.md`\n - `docs/BETTY_TEST_LOG.md`\n- Artifacts:\n - `artifacts/model_switcher_postgres_password_sync.txt`\n - `artifacts/model_switcher_operator_health.json`\n - `artifacts/model_switcher_reset_start.json`\n - `artifacts/model_switcher_lyrics_suggestion.json`\n - `artifacts/model_switcher_dolphin_switch.json`\n - `artifacts/model_switcher_dolphin_chat.json`\n - `artifacts/model_switcher_music_switch.json`\n - `artifacts/model_switcher_music_chat.json`\n - `artifacts/model_switcher_off.json`\n - `artifacts/model_switcher_mode_audit_count.txt`\n - `artifacts/model_switcher_acceptance_check.txt`\n - `artifacts/model_switcher_docker_ps.txt`\n - `artifacts/model_switcher_reset_final.json`\n- Result: PASS\n- Notes:\n - safe specialist model switching now lives in the operator, with session persistence on `operator_threads.current_model_mode`\n - task-model suggestion requests such as `What model do you have for lyrics?` now answer directly without falling through to lookup or memory\n - specialist responses route through the local Ollama wrapper only when a specialist mode is active; memory, approval-gated actions, and explicit grounded lookup cues keep their existing backend-first paths\n - mode changes are audited in `operator_mode_audit`\n - the smoke temporarily aliases `llama3.1:8b` under the Dolphin and Music model tags to prove routing against the current DGX inventory, then removes those aliases during cleanup\n\n### 2026-03-19 - Betty DeepSeek Provider Mode Extension\n\n- Commands run:\n - `python -m py_compile services/operator/main.py`\n - `docker compose config`\n - `bash scripts/test-model-switcher.sh`\n- Files changed:\n - `services/operator/main.py`\n - `docker-compose.yml`\n - `.env.example`\n - `scripts/test-model-switcher.sh`\n - `scripts/pull-specialist-models.sh`\n - `README.md`\n - `docs/BETTY_STATE.md`\n - `docs/BETTY_TEST_LOG.md`\n- Artifacts:\n - `artifacts/model_switcher_operator_health.json`\n - `artifacts/model_switcher_deepseek_switch.json`\n - `artifacts/model_switcher_deepseek_chat.json`\n - `artifacts/model_switcher_reason_switch.json`\n - `artifacts/model_switcher_reason_chat.json`\n - `artifacts/model_switcher_acceptance_check.txt`\n - `artifacts/model_switcher_docker_ps.txt`\n- Result: PASS\n- Notes:\n - Betty now supports `Betty deepseek now` and `Betty reason now` inside the same per-session mode switcher\n - `deepseek-chat` and `deepseek-reasoner` are wired directly against the configured DeepSeek API base URL instead of the local Ollama wrapper\n - DeepSeek turns record token usage plus estimated spend in `provider_usage_events`, so the existing cost caps and warnings still apply\n - the switcher now normalizes local model names with `:latest`, fixing false negatives for pulled models like `llamusic/llamusic`\n\n### 2026-03-19 - Betty Default Assistant Chat Fix\n\n- Commands run:\n - `python -m py_compile services/operator/main.py`\n - `docker compose config`\n - `docker compose up -d --build betty-operator`\n - `bash scripts/test-default-assistant.sh`\n- Files changed:\n - `services/operator/main.py`\n - `docker-compose.yml`\n - `.env.example`\n - `scripts/test-default-assistant.sh`\n - `README.md`\n - `docs/BETTY_STATE.md`\n - `docs/BETTY_TEST_LOG.md`\n- Artifacts:\n - `artifacts/default_assistant_capability.json`\n - `artifacts/default_assistant_model_status.json`\n - `artifacts/default_assistant_chat.json`\n - `artifacts/default_assistant_acceptance_check.txt`\n- Result: PASS\n- Notes:\n - default mode now has a real assistant chat backend instead of falling through to lookup for every non-tool question\n - capability questions such as calendar access are answered directly by the operator\n - explicit model-status questions now answer truthfully from backend state instead of letting a model improvise provider details\n\n### 2026-03-19 - Betty Local Calendar Honesty Fix\n\n- Commands run:\n - `python -m py_compile services/actions/main.py services/operator/main.py`\n - `docker compose up -d --build betty-actions betty-operator`\n - `bash scripts/test-calendar-local-honesty.sh`\n - `bash scripts/test-default-assistant.sh`\n- Files changed:\n - `services/actions/main.py`\n - `services/operator/main.py`\n - `scripts/test-calendar-local-honesty.sh`\n - `scripts/test-default-assistant.sh`\n - `docs/BETTY_TEST_LOG.md`\n- Artifacts:\n - `artifacts/calendar_local_reset.json`\n - `artifacts/calendar_local_prompt.json`\n - `artifacts/calendar_local_yes.json`\n - `artifacts/calendar_local_where.json`\n - `artifacts/calendar_local_events.json`\n - `artifacts/calendar_local_acceptance_check.txt`\n - `artifacts/default_assistant_capability.json`\n - `artifacts/default_assistant_model_status.json`\n - `artifacts/default_assistant_chat.json`\n - `artifacts/default_assistant_acceptance_check.txt`\n- Result: PASS\n- Notes:\n - generic appointment phrasing such as `make a calendar entry for ...` now routes into the approval-gated action service instead of falling through to chat\n - Betty now states explicitly that approved events are stored in her local Postgres-backed calendar and that there is no calendar review screen in the app yet\n - confirmation and storage responses render in Betty's configured Pacific timezone instead of raw UTC\n - Google Calendar sync is still not wired; this fix only makes the existing local calendar path truthful and usable\n\n### 2026-03-19 - Betty Browser Voice And Calendar Review Baseline\n\n- Commands run:\n - `python -m py_compile services/operator/main.py services/actions/main.py`\n - `docker compose up -d --build betty-actions betty-operator`\n - `bash scripts/test-browser-voice-baseline.sh`\n- Files changed:\n - `services/operator/main.py`\n - `services/operator/static/index.html`\n - `services/operator/static/app.js`\n - `services/operator/static/app.css`\n - `services/operator/Dockerfile`\n - `services/actions/main.py`\n - `docker-compose.yml`\n - `.env.example`\n - `scripts/test-browser-voice-baseline.sh`\n - `README.md`\n - `docs/BETTY_STATE.md`\n - `docs/BETTY_TEST_LOG.md`\n - `docs/BETTY_NEXT.md`\n- Artifacts:\n - `artifacts/browser_voice_operator_health.json`\n - `artifacts/browser_voice_index.html`\n - `artifacts/browser_voice_google_status.json`\n - `artifacts/browser_voice_calendar_reset.json`\n - `artifacts/browser_voice_calendar_prompt.json`\n - `artifacts/browser_voice_calendar_yes.json`\n - `artifacts/browser_voice_calendar_events.json`\n - `artifacts/browser_voice_calendar_where.json`\n - `artifacts/browser_voice_tts_seed.json`\n - `artifacts/browser_voice_input.wav`\n - `artifacts/browser_voice_roundtrip.json`\n - `artifacts/browser_voice_acceptance_check.txt`\n - `artifacts/browser_voice_docker_ps.txt`\n- Result: PASS\n- Notes:\n - the PWA now supports browser microphone capture, local faster-whisper transcription, and automatic local Betty voice playback\n - the PWA now includes a Calendar panel that lists Betty's local calendar entries and exposes Google Calendar connection status\n - Google Calendar OAuth connect, calendar listing, and target selection are implemented, but the live stack still reports `configured=false` until real OAuth client credentials are set\n - Google Calendar writes are only available after OAuth cutover; the existing Google API key alone is not sufficient for private calendar writes\n\n### 2026-03-24 - Betty Lookup Dispatch Regression Fix\n\n- Commands run:\n - `python -m py_compile services/operator/main.py`\n - `docker compose up -d --build betty-operator`\n - `bash scripts/test-lookup-dispatch.sh`\n- Files changed:\n - `services/operator/main.py`\n - `services/lookup/main.py`\n - `scripts/test-lookup-dispatch.sh`\n - `docs/BETTY_TEST_LOG.md`\n- Artifacts:\n - `artifacts/lookup_dispatch_capability.json`\n - `artifacts/lookup_dispatch_actionable_preface.json`\n - `artifacts/lookup_dispatch_imperative.json`\n - `artifacts/lookup_dispatch_acceptance_check.txt`\n- Result: PASS\n- Notes:\n - lookup-capability detection was narrowed so imperative search requests no longer get trapped in the capability-answer branch\n - mixed prompts such as `Do you have access to Brave Search? I want an update...` now dispatch grounded lookup instead of repeating `I can do grounded lookup`\n - pure capability questions such as `Do you have access to Brave Search?` still answer as a capability check\n - Brave Answers fallback text is normalized so Betty explicitly says she ran grounded lookup instead of opening with a generic no-Internet disclaimer\n"}}}} |