FROM python:3.10 ENV PYTHONDONTWRITEBYTECODE=1 ENV DOCKER_CONTAINER=true WORKDIR /app # ── Layer 1: System deps (rarely changes, cached long-term) ── RUN apt-get update && apt-get install -y \ libgl1 \ libglib2.0-0 \ ffmpeg \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* # ── Layer 2a: pip ── # pip is PINNED, not upgraded to the newest. pip 26.3 refuses autobahn==23.1.2 # (requirements.txt): "Distributions from PyPI cannot declare direct URL # dependencies" (its metadata names web3 by a git URL under the `all` extra). # The floating upgrade took 26.3 on 2026-11-10 and central's deploy of # ca0852c0f failed at the requirements step; 26.2.2 is what the last good # deploy (9180ba44d, 2026-10-30) built with. Raise the pin only together # with an autobahn pin that 26.3+ accepts. RUN pip install "pip==26.2.2" # ── Layer 2b: Google/gRPC stack (biggest backtracking source — install first) ── # Installing these first pins all their transitive deps, so the main install # doesn't backtrack through thousands of version combinations. RUN pip install --no-cache-dir \ "grpcio==1.57.0" \ "grpcio-status==1.57.0" \ "google-cloud-aiplatform==1.130.0" \ "google-cloud-bigquery==3.13.0" \ "google-cloud-storage==3.9.0" \ "google-cloud-resource-manager==1.10.4" \ "google-api-python-client==2.190.0" \ "google-auth==2.49.0" \ "google-api-core==2.28.0" \ "googleapis-common-protos==1.59.1" \ "grpc-google-iam-v1==0.14.2" \ "proto-plus==1.22.3" \ "protobuf==4.23.3" \ "google-genai==1.56.0" \ "langchain-google-genai==4.2.1" # ── Layer 2c: LLM/AI stack (second backtracking source) ── RUN pip install --no-cache-dir \ "openai==1.82.0" \ "anthropic==0.83.0" \ "langchain-classic==1.0.1" \ "langchain-community==0.4.1" \ "langchain-anthropic==1.0.0" \ "langchain-core==1.2.15" \ "langchain-text-splitters==1.1.1" \ "langchain-groq==1.1.2" # ── Layer 2d: Remaining requirements ── # Use --no-deps for requirements.txt because layers 2b/2c already resolved # the heavy Google/LLM transitive deps. This avoids resolution-too-deep. COPY requirements.txt . COPY agent-ledger-opensource/ ./agent-ledger-opensource/ RUN pip install --no-cache-dir --no-deps -r requirements.txt && \ pip install --no-cache-dir ./agent-ledger-opensource && \ pip install --no-cache-dir \ autogen-agentchat==0.2.37 \ apscheduler \ autobahn==23.1.2 \ "autobahn[serialization]" \ "autobahn[twisted]" \ "autogen-agentchat[long-context]~=0.2" \ json-repair \ bs4 \ youtube-transcript-api # ── Layer 3: Application code (rebuilds on any code change — fast, no pip) ── COPY . . RUN touch /app/langchain.log # HevolveAI source protection: compile .py → .pyc, strip source, clean metadata RUN python scripts/compile_hevolveai.py --strip-source \ --manifest-out security/hevolveai_manifest.json 2>/dev/null || true && \ find /usr/local/lib -path '*hevolveai*dist-info/direct_url.json' -delete 2>/dev/null || true && \ find /usr/local/lib -path '*embodied*ai*dist-info/direct_url.json' -delete 2>/dev/null || true && \ find /usr/local/lib -path '*hevolveai*dist-info/RECORD' -delete 2>/dev/null || true && \ find /usr/local/lib -path '*embodied*ai*dist-info/RECORD' -delete 2>/dev/null || true # ── Layer 4: LiveKit SFU binary (regional/flat — voice/video for >4-party calls) ── # Pre-stage the official livekit-server Go binary at /usr/local/bin so # the supervisor in integrations.social.livekit_supervisor doesn't need # to download it at first start. Skipped automatically at runtime when # HEVOLVE_DEPLOY_MODE=central or LIVEKIT_DISABLE=1 (cloud / sync-only # deployments use Dockerfile.prod which omits this entire layer). # # The supervisor checks ~/.hevolve/livekit/livekit-server first AND # falls through to PATH lookup, so dropping it at /usr/local/bin works. ARG LIVEKIT_VERSION=1.7.2 RUN set -eux; \ arch="$(dpkg --print-architecture)"; \ case "$arch" in \ amd64) tag=linux_amd64 ;; \ arm64) tag=linux_arm64 ;; \ *) echo "Unsupported arch $arch for livekit-server" >&2; exit 1 ;; \ esac; \ url="https://github.com/livekit/livekit/releases/download/v${LIVEKIT_VERSION}/livekit_${LIVEKIT_VERSION}_${tag}.tar.gz"; \ curl -fsSL "$url" -o /tmp/lk.tgz && \ tar -xzf /tmp/lk.tgz -C /tmp && \ install -m 0755 /tmp/livekit-server /usr/local/bin/livekit-server && \ rm -f /tmp/lk.tgz /tmp/livekit-server # Durable state gets a named, mountable path. It used to have neither. # # core.platform_paths.get_data_dir() falls through to a platform default, which # inside this image is /root/.config/nunba. That is the container's writable # layer, so everything written there dies with the container. Nothing in the # Dockerfile, .env.example or any doc said so, which is how it went unnoticed. # # It was not theoretical. The marketing funnel counts every click, download, # install and signup into /marketing_clicks.jsonl, and the rebuild on # 2026-08-18 took the total from 29 events to 0. Channel attribution is the one # number that cannot be reconstructed after the fact: you cannot go back and # ask where those people came from. A campaign run against this would report # honest-looking zeros the morning after any deploy. # # NUNBA_DATA_DIR is read first by get_data_dir(), so pointing it at a path # under /app makes the location predictable and mountable. VOLUME declares the # intent and keeps the data out of the image layer even when an operator # forgets the -v; it is NOT a substitute for one, because a fresh container # gets a fresh anonymous volume. Bind it: # # -v /opt/hzai-LLM-Langchain-Chatbot-Agent/data:/app/data ENV NUNBA_DATA_DIR=/app/data VOLUME ["/app/data"] # LiveKit RTC ports: WS signaling 7880, TCP fallback 7881, UDP range # 50000-60000 for media. Operators expose only what their NAT requires. EXPOSE 6777 7880 7881 # This CMD runs main(), which is the FULL boot: hevolve_verify_boot(), # guardrail hash enforcement, _validate_startup(), EventBus, local Crossbar # subscribers, runtime-tools restoration, HART skill registry, the agent-engine # daemon supervisor + Phase-2 goal bootstrap, then _serve_app -> hypercorn with # the /peer_link websocket listener mounted. # # This is what the live central node (192.168.0.9, container `langchain`, # port 6777) actually runs as of 2026-08-19. # # deploy/cloud/Dockerfile.prod is the OTHER variant: it serves # `hart_intelligence_entry:app` through gunicorn, which skips main() entirely # and cannot serve websockets. Read its header before switching between them. CMD [ "python", "hart_intelligence_entry.py" ]