diff --git a/.gitea/workflows/build-next.yml b/.gitea/workflows/build-next.yml index e2cc93b..ca34d4a 100644 --- a/.gitea/workflows/build-next.yml +++ b/.gitea/workflows/build-next.yml @@ -1,4 +1,4 @@ -name: Build and Push Next App +name: Build and Push Spoon Images on: push: @@ -33,7 +33,7 @@ jobs: printf '%s\n' "$DOTENV_PROD" > "$env_file" bunx dotenv -e "$env_file" -- env NODE_ENV=test SKIP_E2E=1 bun run ci:check - build-next: + build-images: needs: [quality] runs-on: ubuntu-latest steps: @@ -44,7 +44,7 @@ jobs: with: bun-version: 1.3.10 - run: bun install --frozen-lockfile - - name: Build image + - name: Build Next image env: DOTENV_PROD: ${{ secrets.DOTENV_PROD }} run: | @@ -52,9 +52,21 @@ jobs: trap 'rm -f "$env_file"' EXIT printf '%s\n' "$DOTENV_PROD" > "$env_file" CI_ENV_FILE="$env_file" ./scripts/build-next-app production - - name: Tag and push image + - name: Build agent images + run: ./scripts/build-agent-images + - name: Tag and push images run: | docker tag spoon-next:latest git.gbrown.org/gib/spoon-next:${{ gitea.sha }} docker tag spoon-next:latest git.gbrown.org/gib/spoon-next:latest docker push git.gbrown.org/gib/spoon-next:${{ gitea.sha }} docker push git.gbrown.org/gib/spoon-next:latest + + docker tag spoon-agent-worker:latest git.gbrown.org/gib/spoon-agent-worker:${{ gitea.sha }} + docker tag spoon-agent-worker:latest git.gbrown.org/gib/spoon-agent-worker:latest + docker push git.gbrown.org/gib/spoon-agent-worker:${{ gitea.sha }} + docker push git.gbrown.org/gib/spoon-agent-worker:latest + + docker tag spoon-agent-job:latest git.gbrown.org/gib/spoon-agent-job:${{ gitea.sha }} + docker tag spoon-agent-job:latest git.gbrown.org/gib/spoon-agent-job:latest + docker push git.gbrown.org/gib/spoon-agent-job:${{ gitea.sha }} + docker push git.gbrown.org/gib/spoon-agent-job:latest diff --git a/AGENTS.md b/AGENTS.md index 6fc7b34..f8b3559 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -16,6 +16,11 @@ Postgres on port 5432 for Convex storage, and the Convex dashboard on port 6791. Agent jobs are opt-in; build `docker/agent-job.Dockerfile` as `spoon-agent-job:latest` before running Docker-backed jobs. +- Gitea CI builds and pushes `spoon-next`, `spoon-agent-worker`, and + `spoon-agent-job` images to `git.gbrown.org/gib`. In production, + `SPOON_AGENT_JOB_IMAGE` should point to + `git.gbrown.org/gib/spoon-agent-job:latest`, and the worker service requires + access to the host Docker socket. ## Protected and generated files diff --git a/README.md b/README.md index 4559e8b..611dcc2 100644 --- a/README.md +++ b/README.md @@ -156,6 +156,56 @@ or job container. +
+Production agent runtime images + +Gitea CI builds and pushes three production images: + +```txt +git.gbrown.org/gib/spoon-next:latest +git.gbrown.org/gib/spoon-agent-worker:latest +git.gbrown.org/gib/spoon-agent-job:latest +``` + +The worker image is the long-running service that polls Convex. The job image is +the isolated workbench that the worker launches for each agent job. For the MVP, +production should use the repo-provided JS/TS workbench image: + +```env +SPOON_AGENT_JOB_IMAGE="git.gbrown.org/gib/spoon-agent-job:latest" +``` + +The job image includes Node 22, Bun, package managers through Corepack, git, +ripgrep, Python, build tools, and the OpenCode CLI. It is not the forked +project's production runtime; it is the agent execution environment. + +Production worker runtime requirements: + +- `spoon-agent-worker` must run as a separate service. +- The worker needs `/var/run/docker.sock` mounted so it can launch job + containers. +- The production Docker host must be logged into `git.gbrown.org` so worker jobs + can pull the private `spoon-agent-job` image. +- `SPOON_WORKER_TOKEN` must match the value stored in Convex production env. +- `spoon-next` needs `SPOON_AGENT_WORKER_URL=http://spoon-agent-worker:3921` and + `SPOON_AGENT_WORKER_INTERNAL_TOKEN` so Next API routes can proxy workspace + file, diff, message, command, and draft PR actions. +- `spoon-agent-worker` also needs `GITHUB_APP_ID` and `GITHUB_APP_PRIVATE_KEY`. + +Useful production checks: + +```sh +docker logs --tail=200 spoon-agent-worker +curl -H "Authorization: Bearer $SPOON_AGENT_WORKER_INTERNAL_TOKEN" \ + http://spoon-agent-worker:3921/health +``` + +For the first production run, use an API-key based AI provider profile. Stored +OpenCode/Codex `auth.json` profiles are supported in settings, but worker-side +auth-file injection is still a follow-up before they can execute jobs. + +
+ ## Architecture
diff --git a/docker/agent-job.Dockerfile b/docker/agent-job.Dockerfile index d2e5004..3d1697b 100644 --- a/docker/agent-job.Dockerfile +++ b/docker/agent-job.Dockerfile @@ -1,4 +1,4 @@ -FROM node:22-bookworm +FROM docker.io/library/node:22-bookworm ENV COREPACK_ENABLE_DOWNLOAD_PROMPT=0 diff --git a/docker/agent-worker.Dockerfile b/docker/agent-worker.Dockerfile index dbb2941..e7c51e9 100644 --- a/docker/agent-worker.Dockerfile +++ b/docker/agent-worker.Dockerfile @@ -1,4 +1,4 @@ -FROM oven/bun:1.3.10 +FROM docker.io/oven/bun:1.3.10 RUN apt-get update \ && apt-get install -y --no-install-recommends \ diff --git a/scripts/build-agent-images b/scripts/build-agent-images new file mode 100755 index 0000000..52bebd9 --- /dev/null +++ b/scripts/build-agent-images @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)" + +docker build -f "$ROOT_DIR/docker/agent-worker.Dockerfile" -t spoon-agent-worker:latest "$ROOT_DIR" +docker build -f "$ROOT_DIR/docker/agent-job.Dockerfile" -t spoon-agent-job:latest "$ROOT_DIR"