# ONE image serves every agent and workflow; TARGET_SLUG selects which.
# Build from the repo root:  docker build -f agent-pipeline/runtime/Dockerfile .
#
# Layer order matters: because definitions are code, a one-word prompt edit
# rebuilds the image. Dependencies and the platform layer are cached, and only
# agents/ + workflows/ invalidate at the very end, so that rebuild stays cheap.
FROM python:3.12-slim-bookworm AS builder

# Copy the uv binary in rather than depending on a uv+python tag combination.
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/

ENV UV_COMPILE_BYTECODE=1 UV_LINK_MODE=copy
WORKDIR /app

# 1. Dependency manifests only -> this layer survives every source change.
# THESE MEMBER MANIFESTS ARE INERT WITH TODAY'S uv, AND THAT WAS MEASURED
# rather than assumed. `uv sync --frozen --no-install-workspace` resolves
# entirely from `uv.lock`: on uv 0.11.26 the step below succeeds with only
# `pyproject.toml` and `uv.lock` in the context and every line beneath this
# comment deleted. What it CANNOT do without the real source is the second
# sync, which installs the workspace — and that source arrives with
# `COPY packages/` further down, so these lines are not what makes it work.
#
# THEY ARE KEPT, AND THE REASON IS READABILITY RATHER THAN RESOLUTION. The
# list is how a reader sees which members this image's graph contains, so an
# INCONSISTENT list is worse than a uniformly inert one: naming
# agentkit-core and not the package beneath it reads as a statement that the
# second is not in the graph. `tests/test_docker_cache_layers.py` keeps the
# set closed under the workspace dependency relation for that reason, and
# keeps it from growing — every file here is part of this layer's CACHE KEY,
# so a manifest nothing needs makes an unrelated edit reinstall every
# dependency.
#
# WORTH DELETING OUTRIGHT, as a change of its own. `uv:latest` is what the
# build pulls, so the measurement above is of a moving target — which is an
# argument for making the decision deliberately rather than by attrition.
COPY pyproject.toml uv.lock ./
COPY packages/elan-agentkit-contracts/pyproject.toml packages/elan-agentkit-contracts/
COPY packages/agentkit-core/pyproject.toml packages/agentkit-core/
COPY agent-pipeline/plugins/pyproject.toml agent-pipeline/plugins/
COPY agent-pipeline/runtime/pyproject.toml agent-pipeline/runtime/
# --package selects ONLY the runtime and its workspace dependencies. Without it
# uv resolves the root aggregate package, which depends on every service and
# would need ledger/rt/observability sources in this image.
RUN --mount=type=cache,target=/root/.cache/uv \
    uv sync --frozen --no-install-workspace --no-dev --package elan-agentkit-runtime

# 2. Platform source — changes only when the runtime or plugins change.
COPY packages/ packages/
COPY agent-pipeline/plugins/ agent-pipeline/plugins/
COPY agent-pipeline/runtime/ agent-pipeline/runtime/
RUN --mount=type=cache,target=/root/.cache/uv \
    uv sync --frozen --no-dev --package elan-agentkit-runtime

# 3. Target definitions LAST — the layer that changes most often.
COPY agent-pipeline/agents/ agent-pipeline/agents/
COPY agent-pipeline/workflows/ agent-pipeline/workflows/


FROM python:3.12-slim-bookworm AS runtime

RUN useradd --create-home --uid 1000 app
WORKDIR /app
COPY --from=builder --chown=app:app /app /app

ENV PATH="/app/.venv/bin:$PATH" \
    PYTHONUNBUFFERED=1 \
    PYTHONDONTWRITEBYTECODE=1 \
    TARGETS_ROOT=/app/agent-pipeline \
    PORT=8080

USER app
EXPOSE 8080
CMD ["sh", "-c", "uvicorn agentkit_runtime.main:app --host 0.0.0.0 --port ${PORT}"]
