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
31 changes: 27 additions & 4 deletions cmd/ax/harness.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package main

import (
"context"
"encoding/base64"
"fmt"
"log"
"net"
Expand Down Expand Up @@ -142,12 +143,34 @@ func runAntigravityInteractionsHarness(ctx context.Context) error {
if err := setHarnessWorkDir(); err != nil {
return err
}
stateDir, err := antigravityinteractions.DefaultStateDir()
if err != nil {
return err

// Read harness config from ax.yaml handed to the actor via AX_CONFIG_CONTENT.
// Fall back to built-in defaults.
var hc config.AntigravityInteractionsHarnessConfig
if raw := os.Getenv("AX_CONFIG_CONTENT"); raw != "" {
if data, err := base64.StdEncoding.DecodeString(raw); err != nil {
log.Printf("AX_CONFIG_CONTENT: base64 decode failed, using defaults: %v", err)
} else if cfg, err := config.LoadFromBytes(data); err != nil {
log.Printf("AX_CONFIG_CONTENT: parse failed, using defaults: %v", err)
} else {
hc = cfg.Harnesses.AntigravityInteractions
}
}
agent := hc.Agent
if agent == "" {
agent = antigravityinteractions.DefaultAgent
}
stateDir := hc.StateDir
if stateDir == "" {
var err error
stateDir, err = antigravityinteractions.DefaultStateDir()
if err != nil {
return err
}
}

cfg := antigravityinteractions.AntigravityInteractionsConfig{
Agent: antigravityinteractions.DefaultAgent,
Agent: agent,
StateDir: stateDir,
}
return antigravityinteractions.Serve(ctx, cfg, harnessHost, harnessPort, harnessReadyzPort)
Expand Down
16 changes: 15 additions & 1 deletion hack/install-ax.sh
Original file line number Diff line number Diff line change
Expand Up @@ -230,16 +230,23 @@ deploy_ax_server() {
echo "Using existing Postgres via AX_EVENTLOG_DSN." >&2
fi

# Base64 of the single-source config file (manifests/ax.yaml), injected into
# each ActorTemplate's AX_CONFIG_CONTENT env so substrate actors read their config
# without a mounted ConfigMap.
local ax_config_content
ax_config_content="$(base64 < manifests/ax.yaml | tr -d '\n')"

# Common substitutions applied to every rendered manifest.
local render_sed=(
-e "s|\${GEMINI_API_KEY}|${GEMINI_API_KEY}|g"
-e "s|\${AX_SNAPSHOTS_BUCKET}|${AX_SNAPSHOTS_BUCKET}|g"
-e "s|\${AX_IMAGE}|${ax_image}|g"
-e "s|\${ATEOM_IMAGE}|${ateom_image}|g"
-e "s|\${GOOGLE_CLOUD_PROJECT}|${GOOGLE_CLOUD_PROJECT:-}|g"
-e "s|\${AX_CONFIG_CONTENT}|${ax_config_content}|g"
)

# Render and apply the core manifest (namespace, harnesses, ax-server, ConfigMap).
# Render and apply the core manifest (namespace, harnesses, ax-server).
if ! sed "${render_sed[@]}" manifests/ax-deployment.yaml | run_kubectl apply -f -; then
echo >&2
echo "Error: cluster rejected the manifest. An \"unknown field\" error usually means the" >&2
Expand All @@ -248,6 +255,13 @@ deploy_ax_server() {
exit 1
fi

# Create/update the controller ConfigMap from the single-source config file
# (manifests/ax.yaml) -- the same file that seeds the actors' AX_CONFIG_CONTENT.
# ax-server mounts it at /etc/ax/ax.yaml.
run_kubectl -n ax create configmap ax-server-config \
--from-file=ax.yaml=manifests/ax.yaml \
--dry-run=client -o yaml | run_kubectl apply -f -

# Create/update the event-log Secret with the DSN (and, for the bundled Postgres,
# its password). ax-server reads AX_EVENTLOG_DSN from this Secret's dsn key.
local secret_args=(--from-literal=dsn="${pg_dsn}")
Expand Down
6 changes: 5 additions & 1 deletion internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,10 +144,14 @@ func LoadFromFile(path string) (*Config, error) {
if err != nil {
return nil, fmt.Errorf("failed to read config file: %w", err)
}
return LoadFromBytes(data)
}

// LoadFromBytes parses configuration from YAML bytes and applies defaults.
func LoadFromBytes(data []byte) (*Config, error) {
var cfg Config
if err := yaml.Unmarshal(data, &cfg); err != nil {
return nil, fmt.Errorf("failed to parse config file: %w", err)
return nil, fmt.Errorf("failed to parse config: %w", err)
}

cfg.setDefaults()
Expand Down
29 changes: 29 additions & 0 deletions internal/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,3 +156,32 @@ harnesses:
t.Errorf("StateDir = %q, want %q", got, want)
}
}

func TestLoadFromBytes(t *testing.T) {
cfg, err := LoadFromBytes([]byte(`
version: v1alpha
harnesses:
antigravity:
default: true
`))
if err != nil {
t.Fatalf("LoadFromBytes: %v", err)
}
if cfg.Version != "v1alpha" {
t.Errorf("Version = %q, want %q", cfg.Version, "v1alpha")
}
if !cfg.Harnesses.Antigravity.Default {
t.Error("Harnesses.Antigravity.Default = false, want true")
}
// setDefaults must run (same as LoadFromFile).
if got, want := cfg.Server.Address, ":8494"; got != want {
t.Errorf("Server.Address = %q, want default %q", got, want)
}
}

// TestLoadFromBytes_Invalid returns an error on malformed YAML.
func TestLoadFromBytes_Invalid(t *testing.T) {
if _, err := LoadFromBytes([]byte("harnesses: [unterminated")); err == nil {
t.Fatal("LoadFromBytes(invalid): got nil error, want error")
}
}
19 changes: 3 additions & 16 deletions manifests/ax-deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,9 @@ spec:
value: "${GOOGLE_CLOUD_PROJECT}"
- name: AX_HARNESS_WORKDIR
value: "/workspace"
# Content of manifests/ax.yaml
- name: AX_CONFIG_CONTENT
value: "${AX_CONFIG_CONTENT}"
readyz:
httpGet:
path: /readyz
Expand Down Expand Up @@ -149,19 +152,3 @@ spec:
- name: ax-config
configMap:
name: ax-server-config
---
apiVersion: v1
kind: ConfigMap
metadata:
name: ax-server-config
namespace: ax
data:
ax.yaml: |
version: v1alpha
eventlog:
postgres:
dsn: ${AX_EVENTLOG_DSN}
harnesses:
antigravity:
default: true
antigravity-interactions: {}
30 changes: 30 additions & 0 deletions manifests/ax.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# Deploy configuration for AX on Agent Substrate. install-ax.sh renders this
# single source into two places:
# - the ax-server-config ConfigMap (mounted at /etc/ax/ax.yaml), read by the
# controller (`ax serve`); and
# - the AX_CONFIG_CONTENT env (base64 of this file) on each ActorTemplate,
# so a substrate actor can read its config.
version: v1alpha

eventlog:
postgres:
dsn: ${AX_EVENTLOG_DSN}

harnesses:
antigravity:
default: true
antigravity-interactions: {}
Loading