name: omni-pipeline

# Offline-safe reference deployment. Every service is behind an explicit profile,
# every image must already exist locally, and model paths are bind-mounted read-only.
# Running `docker compose -f docker-compose.pipeline.yml config` starts nothing.

x-runtime-defaults: &runtime-defaults
  init: true
  pull_policy: never
  restart: "no"
  networks:
    - omni-internal
  environment: &offline-environment
    HF_HUB_OFFLINE: "1"
    TRANSFORMERS_OFFLINE: "1"
    HF_DATASETS_OFFLINE: "1"
    VLLM_USE_MODELSCOPE: "0"
    VLLM_NO_USAGE_STATS: "1"
    DO_NOT_TRACK: "1"
    NO_PROXY: "127.0.0.1,localhost,gateway,minicpm-worker,conductor,asr,router,qwen,manager,media,h3-controller,h3-worker,hunyuan"

# Documentation-only hardware profiles consumed by the OMNI manager/config
# generator. GPU access is intentionally supplied by a host-specific override;
# the base file must never silently select an NVIDIA runtime on an AMD host.
x-hardware-profiles:
  mi300x_single:
    status: recommended
    device_count: 1
    per_device_memory_gib: 192
    preferred_precision: bf16
    rocm_devices:
      - /dev/kfd
      - /dev/dri
    colocate_minicpm_and_qwen: probe_first
    dflash2: capability_and_benchmark_gated
  mi50_12x16_legacy:
    status: legacy_unsupported_current_rocm
    device_count: 12
    per_device_memory_gib: 16
    memory_is_not_one_aggregate_pool: true
    automatic_selection: false
    expected_service_level: degraded_non_realtime

services:
  gateway:
    <<: *runtime-defaults
    profiles:
      - core
    image: "${MINICPM_GATEWAY_IMAGE:-minicpm-gateway:local}"
    command:
      - --host
      - 0.0.0.0
      - --port
      - "8006"
      - --internal-port
      - "8007"
      - --http
    ports:
      - "127.0.0.1:${OMNI_PUBLIC_PORT:-8006}:8006"
    expose:
      - "8007"
    volumes:
      - type: bind
        source: "${OMNI_STATE_DIR:-./.state}"
        target: /app/.state
      - type: bind
        source: "${OMNI_ARTIFACT_DIR:-./artifacts}"
        target: /app/artifacts
    environment:
      <<: *offline-environment
      OMNI_CONDUCTOR_URL: "http://conductor:8100"
      OMNI_CONDUCTOR_HTTP_URL: "http://conductor:8100"
      OMNI_CONDUCTOR_WS_URL: "ws://conductor:8100"
      # Required because the conductor listens on the container network.  Do
      # not replace this with a checked-in default secret.
      OMNI_CONDUCTOR_TOKEN: "${OMNI_CONDUCTOR_TOKEN:?set OMNI_CONDUCTOR_TOKEN to a strong local secret}"
      OMNI_PUBLIC_CONTROL_PREFIX: "/v1/agent"
    networks:
      - omni-edge
      - omni-internal

  minicpm-worker:
    <<: *runtime-defaults
    profiles:
      - minicpm
    image: "${MINICPM_WORKER_IMAGE:-minicpm-worker-backend:local}"
    expose:
      - "22400"
      - "22500"
    environment:
      <<: *offline-environment
      GPU_ID: "0"
      BACKEND_PORT: "22500"
      WORKER_PORT: "22400"
      MODEL_PATH: /models/minicpm-o-4_5
      MODEL_REVISION: "${MINICPM_MODEL_REVISION}"
      MODEL_DTYPE: fp16
      LOCAL_FILES_ONLY: "1"
      ENABLE_SPECULATIVE_DECODING: "0"
      GATEWAY_REGISTRY_URL: "http://gateway:8007/internal/workers"
      WORKER_ID: minicpm-worker-0
      WORKER_ENDPOINT: "minicpm-worker:22400"
    volumes:
      - type: bind
        source: "${MINICPM_MODEL_PATH}"
        target: /models/minicpm-o-4_5
        read_only: true
    shm_size: "${MINICPM_SHM_SIZE:-16gb}"

  conductor:
    <<: *runtime-defaults
    profiles:
      - core
    image: "${OMNI_CONTROL_IMAGE:-omni-control:local}"
    command:
      - sh
      - -lc
      - "${OMNI_CONDUCTOR_COMMAND:-python -m orchestrator.main}"
    expose:
      - "8100"
    ports:
      - "127.0.0.1:${OMNI_CONDUCTOR_HOST_PORT:-8100}:8100"
    environment:
      <<: *offline-environment
      OMNI_CONDUCTOR_HOST: 0.0.0.0
      OMNI_CONDUCTOR_PORT: "8100"
      OMNI_CONDUCTOR_TOKEN: "${OMNI_CONDUCTOR_TOKEN:?set OMNI_CONDUCTOR_TOKEN to a strong local secret}"
      OMNI_ASR_URL: "ws://asr:8101/v1/stream"
      # Deterministic routing lives inside the conductor. A learned router is
      # external opt-in only and has no default Compose service.
      OMNI_ROUTER_URL: "${OMNI_ROUTER_URL:-http://127.0.0.1:8102/v1}"
      OMNI_QWEN_URL: "http://qwen:8103/v1"
      OMNI_MANAGER_URL: "http://manager:8110"
      OMNI_MEDIA_URL: "http://media:8111"
      OMNI_HERMES_URL: "http://host.docker.internal:8642"
      # Must equal the host Hermes API_SERVER_KEY. This inbound Hermes key is
      # intentionally separate from OPENAI_API_KEY/QWEN_API_KEY.
      HERMES_API_KEY: "${HERMES_API_KEY:-}"
      OMNI_ALLOW_REMOTE_ENDPOINTS: "1"
      OMNI_ROUTER_ENABLED: "${OMNI_ROUTER_ENABLED:-0}"
      OMNI_EVENT_STORE_PATH: /state/orchestrator/events.v1.jsonl
      OMNI_HARDWARE_PROFILE: "${OMNI_HARDWARE_PROFILE:-mi300x_single}"
      OMNI_REMOTE_NODES_ENABLED: "${OMNI_REMOTE_NODES_ENABLED:-0}"
      OMNI_MDNS_DISCOVERY_ENABLED: "${OMNI_MDNS_DISCOVERY_ENABLED:-0}"
      OMNI_REMOTE_LISTENER_ENABLED: "0"
      OMNI_NODE_LISTEN_HOST: "127.0.0.1"
      OMNI_MTLS_REQUIRED: "1"
      OMNI_REMOTE_CAPABILITIES_DEFAULT: deny
      OMNI_REMOTE_WEBCAM_DEFAULT: deny
      OMNI_REMOTE_EMERGENCY_STOP_ENABLED: "1"
      OMNI_REMOTE_AUDIT_PATH: /state/audit/remote-nodes.v1.jsonl
      HERMES_ENABLE_INTERNET_SEARCH: "${HERMES_ENABLE_INTERNET_SEARCH:-1}"
      HERMES_SEARCH_API_KEY: "${HERMES_SEARCH_API_KEY:-}"
    volumes:
      - type: bind
        source: "${OMNI_STATE_DIR:-./.state}"
        target: /state
    extra_hosts:
      - "host.docker.internal:host-gateway"
    networks:
      - omni-internal
      - omni-hostbridge

  asr:
    <<: *runtime-defaults
    profiles:
      - tools
    image: "${ASR_IMAGE:-omni-asr:local}"
    command:
      - sh
      - -lc
      - "${ASR_SERVER_COMMAND:-python -m asr_service}"
    expose:
      - "8101"
    environment:
      <<: *offline-environment
      # Explicit container-only exception: port 8101 is not published to the
      # host and remains reachable solely on omni-internal.
      OMNI_ASR_HOST: "0.0.0.0"
      OMNI_ASR_PORT: "8101"
      OMNI_ASR_ALLOW_CONTAINER_BIND: "1"
      # In-process Whisper loads only the explicitly mounted local path.
      OMNI_ASR_PROVIDER: "${OMNI_ASR_PROVIDER:-transformers_local}"
      OMNI_ASR_ENDPOINT: "${OMNI_ASR_ENDPOINT:-}"
      OMNI_ASR_MODEL: "${OMNI_ASR_MODEL:-/models/asr}"
      OMNI_ASR_DEVICE: "${OMNI_ASR_DEVICE:-cuda:0}"
      # MI300X quality default. Unsupported BF16 fails closed; there is no
      # silent dtype fallback inside the local provider.
      OMNI_ASR_DTYPE: "${OMNI_ASR_DTYPE:-bfloat16}"
      OMNI_ASR_MAX_PROVIDER_CONCURRENCY: "1"
      OMNI_ASR_API_KEY: "${OMNI_ASR_API_KEY:-}"
      ASR_MODEL_PATH: /models/asr
      ASR_MODEL_REVISION: "${ASR_MODEL_REVISION}"
      LOCAL_FILES_ONLY: "1"
    volumes:
      - type: bind
        source: "${ASR_MODEL_PATH}"
        target: /models/asr
        read_only: true

  qwen:
    <<: *runtime-defaults
    profiles:
      - tools
    image: "${QWEN_VLLM_IMAGE:-qwen-vllm:local}"
    command:
      - python
      - -m
      - qwen_runtime.launcher
    # Hermes runs on the host/WSL2. This is a private loopback publication,
    # never a LAN listener; the Qwen container itself remains on the internal net.
    ports:
      - "127.0.0.1:${QWEN_HOST_PORT:-8103}:8103"
    environment:
      <<: *offline-environment
      MODEL_PATH: /models/qwen
      MODEL_ID: "${QWEN_EXACT_MODEL_ID}"
      MODEL_REVISION: "${QWEN_EXACT_MODEL_REVISION}"
      QWEN_CHECKPOINT_MANIFEST_PATH: /models/qwen/.omni-checkpoint.json
      QWEN_CHECKPOINT_MANIFEST_SHA256: "${QWEN_CHECKPOINT_MANIFEST_SHA256}"
      PYTHONPATH: /opt/omni
      VLLM_BASELINE_COMMIT: "${QWEN_VLLM_BASELINE_COMMIT}"
      INFERENCE_MODE: "${QWEN_INFERENCE_MODE:-auto}"
      QWEN_BIND_HOST: 0.0.0.0
      QWEN_PORT: "8103"
      QWEN_CONTAINER_INTERNAL_ONLY: "1"
      QWEN_SERVED_MODEL_NAME: qwen3.8-27b
      QWEN_API_KEY: "${QWEN_API_KEY:-omni-local-only}"
      QWEN_MAX_MODEL_LEN: "${QWEN_MAX_MODEL_LEN:-65536}"
      QWEN_GPU_MEMORY_UTILIZATION: "${QWEN_GPU_MEMORY_UTILIZATION:-0.60}"
      QWEN_DTYPE: "${QWEN_DTYPE:-float16}"
      DFLASH2_SPECULATOR_PATH: /models/dflash2
      DFLASH2_SPECULATOR_REVISION: "${QWEN_DFLASH2_SPECULATOR_REVISION:-disabled}"
      DFLASH2_MANIFEST_SHA256: "${QWEN_DFLASH2_MANIFEST_SHA256:-disabled}"
      DFLASH2_VLLM_COMMIT: "${QWEN_DFLASH2_VLLM_COMMIT:-disabled}"
      QWEN_DFLASH2_CANARY: "${QWEN_DFLASH2_CANARY:-0}"
      QWEN_DFLASH2_GATE_PATH: /models/dflash2/.omni-gate.json
      QWEN_DFLASH2_GATE_SHA256: "${QWEN_DFLASH2_GATE_SHA256:-disabled}"
      QWEN_DFLASH2_MIN_P95_IMPROVEMENT_PERCENT: "${QWEN_DFLASH2_MIN_P95_IMPROVEMENT_PERCENT:-5.0}"
      DFLASH2_AMD_CAPABILITY_GATED: "1"
      DFLASH2_REQUIRE_TARGET_BENCHMARK: "1"
      NATIVE_MTP_TOKENS: "${QWEN_NATIVE_MTP_TOKENS:-1}"
      OMNI_HARDWARE_PROFILE: "${OMNI_HARDWARE_PROFILE:-mi300x_single}"
      LOCAL_FILES_ONLY: "1"
    volumes:
      - type: bind
        source: ./qwen_runtime
        target: /opt/omni/qwen_runtime
        read_only: true
      - type: bind
        source: "${QWEN_MODEL_PATH}"
        target: /models/qwen
        read_only: true
      - type: bind
        source: "${QWEN_DFLASH2_SPECULATOR_PATH:-./.models/dflash2-disabled}"
        target: /models/dflash2
        read_only: true
    shm_size: "${QWEN_SHM_SIZE:-24gb}"

  manager:
    <<: *runtime-defaults
    profiles:
      - core
    image: "${OMNI_CONTROL_IMAGE:-omni-control:local}"
    command:
      - sh
      - -lc
      - "${OMNI_MANAGER_COMMAND:-python -m omni_manager}"
    expose:
      - "8110"
    environment:
      <<: *offline-environment
      OMNI_MANAGER_HOST: "0.0.0.0"
      OMNI_MANAGER_PORT: "8110"
      OMNI_MANAGER_ALLOW_CONTAINER_BIND: "1"
      OMNI_MANAGER_ALLOW_CONTAINER_ENDPOINTS: "1"
      OMNI_MANAGER_MEDIA_URL: "http://media:8111"
      OMNI_MANAGER_LAUNCH_ENABLED: "0"
      OMNI_MANAGER_VIDEO_LAUNCH_URL: "${OMNI_MANAGER_VIDEO_LAUNCH_URL:-}"
      OMNI_MANAGER_IMAGE_LAUNCH_URL: "${OMNI_MANAGER_IMAGE_LAUNCH_URL:-}"
      OMNI_MANAGER_VIDEO_RELEASE_URL: "${OMNI_MANAGER_VIDEO_RELEASE_URL:-}"
      OMNI_MANAGER_IMAGE_RELEASE_URL: "${OMNI_MANAGER_IMAGE_RELEASE_URL:-}"
      OMNI_MANAGER_LOCAL_FILES_ONLY: "1"
      OMNI_HARDWARE_PROFILE: "${OMNI_HARDWARE_PROFILE:-mi300x_single}"

  media:
    <<: *runtime-defaults
    profiles:
      - core
    image: "${OMNI_CONTROL_IMAGE:-omni-control:local}"
    command:
      - sh
      - -lc
      - "${OMNI_MEDIA_COMMAND:-python -m omni_media}"
    expose:
      - "8111"
    environment:
      <<: *offline-environment
      OMNI_MEDIA_HOST: "0.0.0.0"
      OMNI_MEDIA_PORT: "8111"
      OMNI_MEDIA_ALLOW_CONTAINER_BIND: "1"
      OMNI_MEDIA_ALLOW_CONTAINER_ENDPOINTS: "1"
      OMNI_MANAGER_URL: "http://manager:8110"
      H3_CONTROLLER_URL: "http://h3-controller:30010"
      HUNYUAN_URL: "http://hunyuan:30020"
      OMNI_MEDIA_AUTO_START: "0"

  h3-controller:
    <<: *runtime-defaults
    profiles:
      - video
    image: "${H3_IMAGE:-minimax-h3:local}"
    command:
      - sh
      - -lc
      - "${H3_CONTROLLER_COMMAND:-python -m h3.controller --host 0.0.0.0 --port 30010 --worker-url http://h3-worker:30011}"
    expose:
      - "30010"
    environment:
      <<: *offline-environment
      H3_MODEL_PATH: /models/h3
      H3_MODEL_REVISION: "${H3_MODEL_REVISION}"
      H3_DTYPE: "${H3_DTYPE:-bfloat16}"
      H3_MODEL_VARIANT: "${H3_MODEL_VARIANT:-fl2va}"
      H3_REQUIRE_PRECISION_PROBE: "1"
      H3_LOAD_ON_START: "0"
      LOCAL_FILES_ONLY: "1"
    volumes:
      - type: bind
        source: "${H3_MODEL_PATH}"
        target: /models/h3
        read_only: true

  h3-worker:
    <<: *runtime-defaults
    profiles:
      - video
    image: "${H3_IMAGE:-minimax-h3:local}"
    command:
      - sh
      - -lc
      - "${H3_WORKER_COMMAND:-python -m h3.worker --host 0.0.0.0 --port 30011 --model /models/h3 --model-variant fl2va --dtype bfloat16 --local-files-only --lazy-load}"
    expose:
      - "30011"
    environment:
      <<: *offline-environment
      H3_MODEL_PATH: /models/h3
      H3_MODEL_REVISION: "${H3_MODEL_REVISION}"
      H3_DTYPE: "${H3_DTYPE:-bfloat16}"
      H3_MODEL_VARIANT: "${H3_MODEL_VARIANT:-fl2va}"
      H3_REQUIRE_PRECISION_PROBE: "1"
      H3_LOAD_ON_START: "0"
      LOCAL_FILES_ONLY: "1"
    volumes:
      - type: bind
        source: "${H3_MODEL_PATH}"
        target: /models/h3
        read_only: true
    shm_size: "${H3_SHM_SIZE:-32gb}"

  hunyuan:
    <<: *runtime-defaults
    profiles:
      - image
    image: "${HUNYUAN_IMAGE:-hunyuan-image:local}"
    command:
      - sh
      - -lc
      - "${HUNYUAN_SERVER_COMMAND:-python -m hunyuan_image.server --host 0.0.0.0 --port 30020 --model /models/hunyuan --local-files-only --lazy-load}"
    expose:
      - "30020"
    environment:
      <<: *offline-environment
      HUNYUAN_MODEL_PATH: /models/hunyuan
      HUNYUAN_MODEL_REVISION: "${HUNYUAN_MODEL_REVISION}"
      HUNYUAN_QUANTIZATION: "${HUNYUAN_QUANTIZATION:-nf4-v2}"
      HUNYUAN_EXTERNAL_SAFETY_CHECKER: "${HUNYUAN_EXTERNAL_SAFETY_CHECKER:-0}"
      HUNYUAN_POOL_MODE: cold
      HUNYUAN_MIN_LOADED: "0"
      HUNYUAN_MAX_LOADED: "1"
      HUNYUAN_LOAD_ON_START: "0"
      HUNYUAN_IDLE_UNLOAD_SECONDS: "${HUNYUAN_IDLE_UNLOAD_SECONDS:-300}"
      LOCAL_FILES_ONLY: "1"
    volumes:
      - type: bind
        source: "${HUNYUAN_MODEL_PATH}"
        target: /models/hunyuan
        read_only: true
    shm_size: "${HUNYUAN_SHM_SIZE:-24gb}"

networks:
  omni-edge:
    driver: bridge
  omni-internal:
    driver: bridge
    internal: true
  omni-hostbridge:
    driver: bridge
