fix(agents): bundle workspace deps into flue runtime
Replace @code/env and @code/primitives imports with local modules so the Node deploy artifact does not resolve workspace packages to TypeScript source at runtime. Switch Docker build to pnpm frozen install, add runner Dockerfile and a liveness health endpoint.
This commit is contained in:
@@ -13,20 +13,36 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
python3 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# pnpm-lock.yaml is authoritative; Bun would migrate it and reject frozen mode.
|
||||
RUN npm install --global pnpm@11.17.0
|
||||
|
||||
COPY . .
|
||||
RUN bun install --frozen-lockfile
|
||||
RUN pnpm install --frozen-lockfile
|
||||
RUN node packages/agents/node_modules/@flue/cli/bin/flue.mjs build --target node --root packages/agents
|
||||
|
||||
FROM node:24-bookworm-slim
|
||||
|
||||
ENV NODE_OPTIONS=--experimental-specifier-resolution=node
|
||||
|
||||
ENV NODE_ENV=production
|
||||
ENV PORT=3000
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=build /app /app
|
||||
COPY --from=bun /usr/local/bin/bun /usr/local/bin/bun
|
||||
|
||||
# Attempt preparation occurs in the Flue handler: it clones repositories and
|
||||
# installs their dependencies before the runner mounts the shared checkout.
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
ca-certificates \
|
||||
git \
|
||||
openssh-client \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& groupadd --system --gid 10001 zopu \
|
||||
&& useradd --system --uid 10001 --gid zopu --home-dir /nonexistent zopu \
|
||||
&& mkdir -p /var/lib/zopu/workspaces \
|
||||
&& chown -R zopu:zopu /app /var/lib/zopu
|
||||
USER zopu
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
CMD ["node", "packages/agents/dist/server.mjs"]
|
||||
|
||||
84
packages/agents/Dockerfile.runner
Normal file
84
packages/agents/Dockerfile.runner
Normal file
@@ -0,0 +1,84 @@
|
||||
# syntax=docker/dockerfile:1.7
|
||||
#
|
||||
# AgentOS runner image for the VDS Compose topology.
|
||||
#
|
||||
# This image is distinct from the production Flue image (Dockerfile): it is the
|
||||
# dedicated runner that registers with the Rivet Engine and owns the persistent
|
||||
# host filesystem used to clone repositories, install dependencies, and mount
|
||||
# isolated checkouts into AgentOS workspaces. It needs Bun + Git + the engine-cli
|
||||
# used by local development, plus Node (required by the Flue/AgentOS runtime).
|
||||
#
|
||||
# The runner is internal-only. It never publishes a port; only the engine and
|
||||
# Compose healthcheck reach it. Secrets are injected at runtime via the Compose
|
||||
# environment, never baked into a layer.
|
||||
#
|
||||
# RIVET_RUNNER_VERSION is a build-time contract: it lets the engine route new
|
||||
# actors to the new runner and drain old ones. CI sets it to the release
|
||||
# identity (commit SHA or build timestamp) for every immutable image it pushes.
|
||||
|
||||
FROM oven/bun:1.3.14 AS bun
|
||||
|
||||
FROM node:24-bookworm-slim AS build
|
||||
|
||||
# Bun is needed to install dependency trees in the cloned workspaces.
|
||||
COPY --from=bun /usr/local/bin/bun /usr/local/bin/bun
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Native build toolchain for optional native deps during install.
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
ca-certificates \
|
||||
g++ \
|
||||
make \
|
||||
python3 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# pnpm-lock.yaml is authoritative; Bun would migrate it and reject frozen mode.
|
||||
RUN npm install --global pnpm@11.17.0
|
||||
|
||||
COPY . .
|
||||
RUN pnpm install --frozen-lockfile
|
||||
|
||||
# Runner image only. No Flue server build is needed: the runner entry point
|
||||
# (src/runner.ts) calls runtimeRegistry.startAndWait() to register with the
|
||||
# engine. Building the Flue Node server here would be dead weight.
|
||||
|
||||
FROM node:24-bookworm-slim AS runner
|
||||
|
||||
# Git and CA certs are required by RepositoryWorkspace to clone user repos and
|
||||
# install dependencies inside the mounted source mirror.
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
ca-certificates \
|
||||
git \
|
||||
openssh-client \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& git --version
|
||||
|
||||
# Carry Bun into the runtime image so `bun install --frozen-lockfile` works on
|
||||
# cloned checkouts and BUN_EXECUTABLE resolves to a known path.
|
||||
COPY --from=bun /usr/local/bin/bun /usr/local/bin/bun
|
||||
|
||||
ARG RIVET_RUNNER_VERSION
|
||||
ENV RIVET_RUNNER_VERSION=${RIVET_RUNNER_VERSION}
|
||||
|
||||
ENV NODE_ENV=production
|
||||
|
||||
WORKDIR /app
|
||||
COPY --from=build /app /app
|
||||
|
||||
# Non-root runtime. Compose mounts the workspace bind directory initialized for
|
||||
# this UID so the process can serve its isolated worktrees.
|
||||
RUN groupadd --system --gid 10001 zopu \
|
||||
&& useradd --system --uid 10001 --gid zopu --create-home --home-dir /home/zopu zopu \
|
||||
&& mkdir -p /var/lib/zopu/workspaces \
|
||||
&& chown -R zopu:zopu /var/lib/zopu /home/zopu
|
||||
|
||||
# Point the runtime at the Bun executable discovered by RepositoryWorkspace.
|
||||
# /home/zopu is the pi agent home mount target used by attempt-runner.ts.
|
||||
ENV BUN_EXECUTABLE=/usr/local/bin/bun \
|
||||
AGENT_WORKSPACE_ROOT=/var/lib/zopu/workspaces
|
||||
|
||||
USER zopu
|
||||
|
||||
# runner.ts is TypeScript and this repository executes it with Bun locally.
|
||||
CMD ["bun", "packages/agents/src/runner.ts"]
|
||||
@@ -3,3 +3,11 @@ import { defineConfig } from "@flue/cli/config";
|
||||
export default defineConfig({
|
||||
target: "node",
|
||||
});
|
||||
|
||||
// The Node deploy artifact must not resolve workspace packages to TypeScript
|
||||
// source at runtime; bundle them with the Flue server instead.
|
||||
export const vite = {
|
||||
ssr: {
|
||||
noExternal: [/^@code\/(?:env|primitives)(?:\/.*)?$/u],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
"run:zopu": "node --env-file=../../.env node_modules/@flue/cli/bin/flue.mjs run zopu"
|
||||
},
|
||||
"dependencies": {
|
||||
"@agentos-software/git": "0.3.3",
|
||||
"@code/env": "workspace:*",
|
||||
"@code/primitives": "workspace:*",
|
||||
"@flue/runtime": "npm:@rivet-dev/labs-flue-runtime@1.0.0-beta.9-rivet.2",
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { parseAgentEnv } from "@code/env/agent";
|
||||
import { WorkAttemptExecutionError } from "@code/primitives/execution-runtime";
|
||||
import { registerProvider } from "@flue/runtime";
|
||||
import type { Fetchable } from "@flue/runtime/routing";
|
||||
import { flue } from "@flue/runtime/routing";
|
||||
import { Hono } from "hono";
|
||||
|
||||
import { internalRoute } from "./auth/internal-route";
|
||||
import { parseAgentEnv } from "./runtime/agent-env";
|
||||
import { runtimeRegistry } from "./runtime/agent-os-registry";
|
||||
import {
|
||||
cancelAgentOsAttempt,
|
||||
executeAgentOsAttempt,
|
||||
} from "./runtime/attempt-runner";
|
||||
import { WorkAttemptExecutionError } from "./runtime/zopu-primitives";
|
||||
|
||||
const agentEnv = parseAgentEnv(process.env);
|
||||
|
||||
@@ -35,6 +35,11 @@ registerProvider(agentEnv.AGENT_MODEL_PROVIDER, {
|
||||
|
||||
const app = new Hono();
|
||||
|
||||
// Unauthenticated liveness probe used by the VDS Compose healthcheck and CI
|
||||
// staging verification. It must not depend on Convex, Rivet, or the model
|
||||
// provider; it only confirms the Flue Node process is accepting requests.
|
||||
app.get("/health", (context) => context.json({ status: "ok" }));
|
||||
|
||||
app.post("/internal/work-attempts/execute", internalRoute, async (context) => {
|
||||
try {
|
||||
return context.json(await executeAgentOsAttempt(await context.req.json()));
|
||||
|
||||
1
packages/agents/src/runtime/agent-env.ts
Normal file
1
packages/agents/src/runtime/agent-env.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { parseAgentEnv } from "../../../env/src/agent";
|
||||
@@ -1,6 +1,7 @@
|
||||
import { makePiAgentOsConfig } from "@code/primitives";
|
||||
import { agentOS, setup } from "@rivet-dev/agentos";
|
||||
|
||||
import { makePiAgentOsConfig } from "./zopu-primitives";
|
||||
|
||||
const MAX_ACP_COMPLETED_MESSAGE_BYTES = 128 * 1024 * 1024;
|
||||
const piConfig = makePiAgentOsConfig();
|
||||
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
import { parseAgentEnv } from "@code/env/agent";
|
||||
import {
|
||||
decodeWorkAttemptExecutionInput,
|
||||
makePiHomeFiles,
|
||||
piSessionEnv,
|
||||
} from "@code/primitives";
|
||||
import type {
|
||||
ExecutionEvent,
|
||||
WorkAttemptExecutionResult,
|
||||
} from "@code/primitives";
|
||||
import { createHostDirBackend } from "@rivet-dev/agentos-core";
|
||||
import { createClient } from "@rivet-dev/agentos/client";
|
||||
import { Effect } from "effect";
|
||||
|
||||
import { parseAgentEnv } from "./agent-env";
|
||||
import type { runtimeRegistry } from "./agent-os-registry";
|
||||
import { classifyRuntimeFailure, executionError } from "./execution-errors";
|
||||
import { RepositoryWorkspace } from "./repository-workspace";
|
||||
import {
|
||||
decodeWorkAttemptExecutionInput,
|
||||
makePiHomeFiles,
|
||||
piSessionEnv,
|
||||
} from "./zopu-primitives";
|
||||
import type {
|
||||
ExecutionEvent,
|
||||
WorkAttemptExecutionResult,
|
||||
} from "./zopu-primitives";
|
||||
|
||||
const event = (
|
||||
sequence: number,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { WorkAttemptExecutionError } from "@code/primitives/execution-runtime";
|
||||
import { WorkAttemptExecutionError } from "./zopu-primitives";
|
||||
|
||||
export const executionError = (
|
||||
message: string,
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
} from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
|
||||
import type { PiHomeFiles } from "@code/primitives";
|
||||
import type { PiHomeFiles } from "./zopu-primitives";
|
||||
|
||||
interface PrepareRepositoryInput {
|
||||
attemptId: string;
|
||||
|
||||
14
packages/agents/src/runtime/zopu-primitives.ts
Normal file
14
packages/agents/src/runtime/zopu-primitives.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
export {
|
||||
decodeWorkAttemptExecutionInput,
|
||||
WorkAttemptExecutionError,
|
||||
} from "../../../primitives/src/execution-runtime";
|
||||
export type {
|
||||
ExecutionEvent,
|
||||
WorkAttemptExecutionResult,
|
||||
} from "../../../primitives/src/execution-runtime";
|
||||
export {
|
||||
makePiAgentOsConfig,
|
||||
makePiHomeFiles,
|
||||
piSessionEnv,
|
||||
} from "../../../primitives/src/agent-os";
|
||||
export type { PiHomeFiles } from "../../../primitives/src/agent-os";
|
||||
Reference in New Issue
Block a user