Skip to content
Merged
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
3 changes: 3 additions & 0 deletions forge-go/e2e/distributed_client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,9 @@ agents:
statusKey := fmt.Sprintf("forge:agent:status:%s:%s", guildID, "echo-agent")
val, err := rdb.Get(context.Background(), statusKey).Result()
if err != nil {
if err == redis.Nil {
return fmt.Errorf("status key %q not written yet", statusKey)
}
return err
}
var status map[string]interface{}
Expand Down
27 changes: 24 additions & 3 deletions forge-go/e2e/echo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,29 @@ func TestLevel1_EchoAgentIntegration(t *testing.T) {
}
}()

// For Docker, wait until the supervisor confirms the container is running before
// sending messages. This separates cold-start time from the message-exchange timeout.
if reqSup == "docker" {
statusKey := fmt.Sprintf("forge:agent:status:%s:%s", guildSpec.ID, agentSpec.ID)
require.NoError(t, waitFor(90*time.Second, 500*time.Millisecond, func() error {
val, err := rdb.Get(context.Background(), statusKey).Result()
if err == redis.Nil {
return fmt.Errorf("container not yet started")
}
if err != nil {
return err
}
var st map[string]interface{}
if err := json.Unmarshal([]byte(val), &st); err != nil {
return err
}
if state, _ := st["state"].(string); state != "running" {
return fmt.Errorf("container state: %q", state)
}
return nil
}), "Docker agent container did not reach running state")
}

// 6. Execute the core test assertions
// Send a payload to the topic the EchoAgent is configured to listen to.
testPayload := map[string]interface{}{
Expand All @@ -175,7 +198,7 @@ func TestLevel1_EchoAgentIntegration(t *testing.T) {
topicIn := fmt.Sprintf("%s:echo_topic", guildSpec.ID)
msg.TopicPublishedTo = topicIn

// Background routine to continually ping the agent until it finishes uvx boots and subscribes
// Background routine to continually ping the agent until it subscribes.
done := make(chan struct{})
go func() {
for {
Expand All @@ -188,8 +211,6 @@ func TestLevel1_EchoAgentIntegration(t *testing.T) {
}
}()

// Wait up to 30 seconds for the EchoAgent to receive, process, and publish a response
// Docker boots usually take ~3s the first time if cached, but we give 30s.
topicOut := fmt.Sprintf("%s:default_topic", guildSpec.ID)
respMsg, err := probeAgent.WaitForMessage(ctx, topicOut, 30*time.Second)
close(done)
Expand Down
5 changes: 5 additions & 0 deletions forge-go/e2e/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ func TestMain(m *testing.M) {
fmt.Fprintf(os.Stderr, "failed to build e2e forge binary: %v\n%s\n", buildErr, string(buildOut))
os.Exit(1)
}

// Pre-pull the default Docker agent image so individual tests don't time out on a cold pull.
if out, err := exec.Command("docker", "pull", "ghcr.io/astral-sh/uv:python3.13-bookworm-slim").CombinedOutput(); err != nil {
fmt.Fprintf(os.Stderr, "docker pre-pull (non-fatal): %v\n%s\n", err, out)
}
}
code := m.Run()
if e2eBaseDir != "" {
Expand Down
21 changes: 15 additions & 6 deletions forge-go/supervisor/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"fmt"
"io"
"log/slog"
"os"
"os/user"
"path/filepath"
"strings"
Expand Down Expand Up @@ -209,7 +210,7 @@ func (d *DockerSupervisor) Launch(ctx context.Context, guildID string, agentSpec

imageRef := entry.Image
if imageRef == "" {
imageRef = "ghcr.io/astral-sh/uv:python3.12-bookworm-slim"
imageRef = "ghcr.io/astral-sh/uv:python3.13-bookworm-slim"
}

if err := d.ensureImage(ctx, imageRef); err != nil {
Expand All @@ -218,13 +219,14 @@ func (d *DockerSupervisor) Launch(ctx context.Context, guildID string, agentSpec

env = append(env, "UV_PROJECT_ENVIRONMENT=/tmp/.venv")

var cleanEnv []string
// Extract UV_CACHE_DIR so we can bind-mount it into the container for caching.
var uvCacheDir string
for _, e := range env {
if !strings.HasPrefix(e, "UV_CACHE_DIR=") {
cleanEnv = append(cleanEnv, e)
if val, ok := strings.CutPrefix(e, "UV_CACHE_DIR="); ok {
uvCacheDir = val
break
}
}
env = cleanEnv

var cmd []string
if entry.Runtime == registry.RuntimeDocker {
Expand Down Expand Up @@ -266,6 +268,13 @@ func (d *DockerSupervisor) Launch(ctx context.Context, guildID string, agentSpec

containerCfg, hostCfg := BuildContainerConfig(agentSpec, entry, guildID, imageRef, cmd, env)

// Mount the UV cache directory so repeated runs reuse cached packages.
if uvCacheDir != "" {
if err := os.MkdirAll(uvCacheDir, 0o755); err == nil {
hostCfg.Binds = append(hostCfg.Binds, fmt.Sprintf("%s:%s:rw,z", uvCacheDir, uvCacheDir))
}
}

// Adjust container config for bridge connectivity.
if bridge != nil {
if bridge.Mode() == BridgeTransportIPC {
Expand Down Expand Up @@ -413,7 +422,7 @@ func (d *DockerSupervisor) relaunchContainer(ctx context.Context, guildID string

imageRef := entry.Image
if imageRef == "" {
imageRef = "ghcr.io/astral-sh/uv:python3.12-bookworm-slim"
imageRef = "ghcr.io/astral-sh/uv:python3.13-bookworm-slim"
}

var cmd []string
Expand Down
6 changes: 3 additions & 3 deletions forge-go/supervisor/docker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ func TestBuildContainerConfig_Airgapped(t *testing.T) {
cmd := []string{"python", "main.py"}
env := []string{"FOO=BAR"}

cCfg, hCfg := BuildContainerConfig(agentSpec, entry, "test-guild", "ghcr.io/astral-sh/uv:python3.12-bookworm-slim", cmd, env)
cCfg, hCfg := BuildContainerConfig(agentSpec, entry, "test-guild", "ghcr.io/astral-sh/uv:python3.13-bookworm-slim", cmd, env)

// Image
if cCfg.Image != "ghcr.io/astral-sh/uv:python3.12-bookworm-slim" {
t.Errorf("expected image ghcr.io/astral-sh/uv:python3.12-bookworm-slim, got %s", cCfg.Image)
if cCfg.Image != "ghcr.io/astral-sh/uv:python3.13-bookworm-slim" {
t.Errorf("expected image ghcr.io/astral-sh/uv:python3.13-bookworm-slim, got %s", cCfg.Image)
}

// Command
Expand Down
Loading