Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions .github/workflows/12-check-unit-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -301,3 +301,74 @@ jobs:
files: services/oss/tests/results/junit.xml
check_name: Application services Unit Test Results
comment_mode: off

run-services-node-unit-tests:
# The agent runner (services/agent) is a standalone Node/pnpm package, not part of the
# Python services suite above. It runs its own vitest unit tests plus a tsc typecheck gate.
# No "has_tests" guard on purpose: this suite is established, so a missing/empty suite must
# FAIL the job (vitest exits non-zero on no test files), not silently skip it.
if: |
github.event_name == 'workflow_dispatch' ||
!github.event.pull_request.draft
runs-on: ubuntu-latest
permissions:
checks: write
pull-requests: write
contents: read
env:
AGENTA_LICENSE: oss
steps:
- uses: actions/checkout@v6

- name: Skip when package selection excludes services
if: github.event_name == 'workflow_dispatch' && !contains(fromJSON('["all","services-only"]'), inputs.packages)
run: exit 0

- name: Set up Node.js
if: github.event_name != 'workflow_dispatch' || contains(fromJSON('["all","services-only"]'), inputs.packages)
uses: actions/setup-node@v4
with:
node-version: '24'

- name: Enable Corepack
if: github.event_name != 'workflow_dispatch' || contains(fromJSON('["all","services-only"]'), inputs.packages)
run: corepack enable

- name: Cache pnpm store
if: github.event_name != 'workflow_dispatch' || contains(fromJSON('["all","services-only"]'), inputs.packages)
uses: actions/cache@v4
with:
path: ~/.pnpm-store
key: ${{ runner.os }}-services-agent-pnpm-${{ hashFiles('services/agent/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-services-agent-pnpm-

- name: Set up pnpm store
if: github.event_name != 'workflow_dispatch' || contains(fromJSON('["all","services-only"]'), inputs.packages)
working-directory: services/agent
run: pnpm config set store-dir ~/.pnpm-store

- name: Install dependencies
if: github.event_name != 'workflow_dispatch' || contains(fromJSON('["all","services-only"]'), inputs.packages)
working-directory: services/agent
run: pnpm install --frozen-lockfile

- name: Typecheck (tsc --noEmit, src + tests + config)
if: github.event_name != 'workflow_dispatch' || contains(fromJSON('["all","services-only"]'), inputs.packages)
working-directory: services/agent
run: pnpm run typecheck

# The code-tool unit test spawns python3 and node end-to-end; both are preinstalled on
# ubuntu runners (node is also set up above), so no setup-python step is needed.
- name: Run agent runner unit tests
if: github.event_name != 'workflow_dispatch' || contains(fromJSON('["all","services-only"]'), inputs.packages)
working-directory: services/agent
run: pnpm run test:unit

- name: Publish agent runner unit test results
if: always() && (github.event_name != 'workflow_dispatch' || contains(fromJSON('["all","services-only"]'), inputs.packages))
uses: EnricoMi/publish-unit-test-result-action@v2
with:
files: services/agent/test-results/junit.xml
check_name: Agent Runner Unit Test Results
comment_mode: off
5 changes: 5 additions & 0 deletions .github/workflows/42-railway-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ jobs:
- agenta-api
- agenta-web
- agenta-services
- agenta-sandbox-agent
arch:
- amd64
- arm64
Expand All @@ -127,6 +128,9 @@ jobs:
- image_name: agenta-services
context: .
dockerfile: services/oss/docker/Dockerfile.gh
- image_name: agenta-sandbox-agent
context: services/agent
dockerfile: services/agent/docker/Dockerfile
# Per-arch config (runner + platform string)
- arch: amd64
runner: ubuntu-24.04
Expand Down Expand Up @@ -215,6 +219,7 @@ jobs:
- agenta-api
- agenta-web
- agenta-services
- agenta-sandbox-agent
steps:
- name: Log in to GHCR
uses: docker/login-action@v3
Expand Down
52 changes: 52 additions & 0 deletions docs/docs/self-host/guides/08-custom-agent-runner-images.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
---
title: Custom Agent Runner Images
sidebar_label: Custom Runner Images
description: Build and configure custom sandbox-agent runner images for self-hosted Agenta
---

The default runner image is:

```bash
ghcr.io/agenta-ai/agenta-sandbox-agent:latest
```

Build a custom image when you need extra system packages, custom certificates, or a pinned
runner build.

## Build

From the repository root:

```bash
docker build \
-f services/agent/docker/Dockerfile \
-t ghcr.io/<org>/agenta-sandbox-agent:<tag> \
services/agent
```

The published image does not bundle Claude Code. If a harness needs Claude Code, the runner
installs it from Anthropic at runtime.

## Configure Compose

```bash
AGENTA_SANDBOX_AGENT_IMAGE_NAME=<org>/agenta-sandbox-agent
AGENTA_SANDBOX_AGENT_IMAGE_TAG=<tag>
```

## Configure Helm

```yaml
agentRunner:
image:
repository: ghcr.io/<org>/agenta-sandbox-agent
tag: <tag>
```

## Configure Railway

```bash
export AGENTA_SANDBOX_AGENT_IMAGE="ghcr.io/<org>/agenta-sandbox-agent:<tag>"
```

Then run the normal Railway deploy flow.
36 changes: 36 additions & 0 deletions hosting/docker-compose/ee/docker-compose.gh.yml
Original file line number Diff line number Diff line change
Expand Up @@ -291,11 +291,17 @@ services:
environment:
- SCRIPT_NAME=/services
- DOCKER_NETWORK_MODE=${DOCKER_NETWORK_MODE:-bridge}
- AGENTA_AGENT_RUNNER_URL=${AGENTA_AGENT_RUNNER_URL:-http://sandbox-agent:8765}
- AGENTA_AGENT_ENABLE_MCP=${AGENTA_AGENT_ENABLE_MCP:-false}
# === NETWORK ============================================== #
networks:
- agenta-ee-gh-network
extra_hosts:
- "host.docker.internal:host-gateway"
# === ORCHESTRATION ======================================== #
depends_on:
sandbox-agent:
condition: service_healthy
# === LABELS =============================================== #
labels:
- "traefik.http.routers.services.rule=PathPrefix(`/services/`)"
Expand All @@ -308,6 +314,36 @@ services:
# === LIFECYCLE ============================================ #
restart: always

sandbox-agent:
# === IMAGE ================================================ #
image: ghcr.io/agenta-ai/${AGENTA_SANDBOX_AGENT_IMAGE_NAME:-internal-ee-agenta-sandbox-agent}:${AGENTA_SANDBOX_AGENT_IMAGE_TAG:-latest}
# === CONFIGURATION ======================================== #
environment:
PORT: "8765"
AGENTA_HOST: ${AGENTA_HOST:-}
AGENTA_API_KEY: ${AGENTA_API_KEY:-}
SANDBOX_AGENT_PROVIDER: ${SANDBOX_AGENT_PROVIDER:-local}
SANDBOX_AGENT_DAYTONA_API_KEY: ${SANDBOX_AGENT_DAYTONA_API_KEY:-}
SANDBOX_AGENT_DAYTONA_API_URL: ${SANDBOX_AGENT_DAYTONA_API_URL:-}
SANDBOX_AGENT_DAYTONA_TARGET: ${SANDBOX_AGENT_DAYTONA_TARGET:-}
SANDBOX_AGENT_DAYTONA_SNAPSHOT: ${SANDBOX_AGENT_DAYTONA_SNAPSHOT:-}
SANDBOX_AGENT_DAYTONA_IMAGE: ${SANDBOX_AGENT_DAYTONA_IMAGE:-}
SANDBOX_AGENT_DAYTONA_INSTALL_PI: ${SANDBOX_AGENT_DAYTONA_INSTALL_PI:-false}
# === NETWORK ============================================== #
networks:
- agenta-ee-gh-network
# === LABELS =============================================== #
labels:
- "traefik.enable=false"
# === LIFECYCLE ============================================ #
restart: always
healthcheck:
test: ["CMD", "node", "-e", "fetch('http://127.0.0.1:8765/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"]
interval: 10s
timeout: 5s
retries: 12
start_period: 20s

postgres:
# === IMAGE ================================================ #
image: postgres:17
Expand Down
18 changes: 12 additions & 6 deletions hosting/docker-compose/ee/env.ee.gh.example
Original file line number Diff line number Diff line change
Expand Up @@ -114,12 +114,18 @@ AGENTA_CRYPT_KEY=replace-me
# CRISP_WEBSITE_ID=

# ================================================================== #
# daytona
# ================================================================== #
# DAYTONA_API_KEY=
# DAYTONA_API_URL=https://app.daytona.io/api
# DAYTONA_SNAPSHOT=daytona-small
# DAYTONA_TARGET=eu
# sandbox-agent
# ================================================================== #
# AGENTA_AGENT_RUNNER_URL=http://sandbox-agent:8765
# AGENTA_AGENT_ENABLE_MCP=false
# AGENTA_SANDBOX_AGENT_IMAGE_NAME=internal-ee-agenta-sandbox-agent
# AGENTA_SANDBOX_AGENT_IMAGE_TAG=latest
# SANDBOX_AGENT_PROVIDER=local
# SANDBOX_AGENT_DAYTONA_API_KEY=
# SANDBOX_AGENT_DAYTONA_API_URL=https://app.daytona.io/api
# SANDBOX_AGENT_DAYTONA_TARGET=eu
# SANDBOX_AGENT_DAYTONA_SNAPSHOT=
# SANDBOX_AGENT_DAYTONA_IMAGE=

# ================================================================== #
# docker
Expand Down
36 changes: 36 additions & 0 deletions hosting/docker-compose/oss/docker-compose.gh.yml
Original file line number Diff line number Diff line change
Expand Up @@ -318,11 +318,17 @@ services:
environment:
- SCRIPT_NAME=/services
- DOCKER_NETWORK_MODE=${DOCKER_NETWORK_MODE:-bridge}
- AGENTA_AGENT_RUNNER_URL=${AGENTA_AGENT_RUNNER_URL:-http://sandbox-agent:8765}
- AGENTA_AGENT_ENABLE_MCP=${AGENTA_AGENT_ENABLE_MCP:-false}
# === NETWORK ============================================== #
networks:
- agenta-oss-gh-network
extra_hosts:
- "host.docker.internal:host-gateway"
# === ORCHESTRATION ======================================== #
depends_on:
sandbox-agent:
condition: service_healthy
# === LABELS =============================================== #
labels:
- "traefik.http.routers.services.rule=PathPrefix(`/services/`)"
Expand All @@ -335,6 +341,36 @@ services:
# === LIFECYCLE ============================================ #
restart: always

sandbox-agent:
# === IMAGE ================================================ #
image: ghcr.io/agenta-ai/${AGENTA_SANDBOX_AGENT_IMAGE_NAME:-agenta-sandbox-agent}:${AGENTA_SANDBOX_AGENT_IMAGE_TAG:-latest}
# === CONFIGURATION ======================================== #
environment:
PORT: "8765"
AGENTA_HOST: ${AGENTA_HOST:-}
AGENTA_API_KEY: ${AGENTA_API_KEY:-}
SANDBOX_AGENT_PROVIDER: ${SANDBOX_AGENT_PROVIDER:-local}
SANDBOX_AGENT_DAYTONA_API_KEY: ${SANDBOX_AGENT_DAYTONA_API_KEY:-}
SANDBOX_AGENT_DAYTONA_API_URL: ${SANDBOX_AGENT_DAYTONA_API_URL:-}
SANDBOX_AGENT_DAYTONA_TARGET: ${SANDBOX_AGENT_DAYTONA_TARGET:-}
SANDBOX_AGENT_DAYTONA_SNAPSHOT: ${SANDBOX_AGENT_DAYTONA_SNAPSHOT:-}
SANDBOX_AGENT_DAYTONA_IMAGE: ${SANDBOX_AGENT_DAYTONA_IMAGE:-}
SANDBOX_AGENT_DAYTONA_INSTALL_PI: ${SANDBOX_AGENT_DAYTONA_INSTALL_PI:-false}
# === NETWORK ============================================== #
networks:
- agenta-oss-gh-network
# === LABELS =============================================== #
labels:
- "traefik.enable=false"
# === LIFECYCLE ============================================ #
restart: always
healthcheck:
test: ["CMD", "node", "-e", "fetch('http://127.0.0.1:8765/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"]
interval: 10s
timeout: 5s
retries: 12
start_period: 20s

postgres:
# === IMAGE ================================================ #
image: postgres:17
Expand Down
18 changes: 12 additions & 6 deletions hosting/docker-compose/oss/env.oss.gh.example
Original file line number Diff line number Diff line change
Expand Up @@ -114,12 +114,18 @@ AGENTA_CRYPT_KEY=replace-me
# CRISP_WEBSITE_ID=

# ================================================================== #
# daytona
# ================================================================== #
# DAYTONA_API_KEY=
# DAYTONA_API_URL=https://app.daytona.io/api
# DAYTONA_SNAPSHOT=daytona-small
# DAYTONA_TARGET=eu
# sandbox-agent
# ================================================================== #
# AGENTA_AGENT_RUNNER_URL=http://sandbox-agent:8765
# AGENTA_AGENT_ENABLE_MCP=false
# AGENTA_SANDBOX_AGENT_IMAGE_NAME=agenta-sandbox-agent
# AGENTA_SANDBOX_AGENT_IMAGE_TAG=latest
# SANDBOX_AGENT_PROVIDER=local
# SANDBOX_AGENT_DAYTONA_API_KEY=
# SANDBOX_AGENT_DAYTONA_API_URL=https://app.daytona.io/api
# SANDBOX_AGENT_DAYTONA_TARGET=eu
# SANDBOX_AGENT_DAYTONA_SNAPSHOT=
# SANDBOX_AGENT_DAYTONA_IMAGE=

# ================================================================== #
# docker
Expand Down
25 changes: 25 additions & 0 deletions services/agent/sandbox-images/daytona/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Daytona Sandbox Snapshot

This folder contains the supported self-host recipe for building a Daytona snapshot for the
Agenta `sandbox-agent` runner path.

We ship the recipe, not a built snapshot. The operator runs it in their own Daytona account:

```bash
DAYTONA_API_KEY=... DAYTONA_TARGET=eu uv run build_snapshot.py --force
```

Configure the runner service with:

```bash
SANDBOX_AGENT_PROVIDER=daytona
SANDBOX_AGENT_DAYTONA_SNAPSHOT=agenta-sandbox-pi
SANDBOX_AGENT_DAYTONA_INSTALL_PI=false
```

The recipe currently bases on the upstream full sandbox-agent image and adds the Pi CLI.
That image includes Claude Code. We do not distribute the resulting snapshot. Cloud builds its
own internal snapshot; self-hosters build their own.

Keep credentials out of the image and snapshot. Provider keys and self-managed login paths are
runtime concerns.
Loading
Loading