Skip to content
Open
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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
22 changes: 22 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,9 @@ jobs:
export KAGENT_LOCAL_HOST=$HOST_IP

export KAGENT_URL="http://$(kubectl get svc -n kagent kagent-controller -o jsonpath='{.status.loadBalancer.ingress[0].ip}'):8083"
export KAGENT_GRPC_URL="$(kubectl get svc -n kagent kagent-controller -o jsonpath='{.status.loadBalancer.ingress[0].ip}'):8084"
echo "KAGENT_URL: $KAGENT_URL"
echo "KAGENT_GRPC_URL: $KAGENT_GRPC_URL"
echo "KAGENT_LOCAL_HOST: $KAGENT_LOCAL_HOST"

# no need to run e2e tests with race, as this will just apply to the test code.
Expand All @@ -132,6 +134,7 @@ jobs:
HOST_IP=$(docker network inspect kind -f '{{range .IPAM.Config}}{{if .Gateway}}{{.Gateway}}{{"\n"}}{{end}}{{end}}' | grep -E '^[0-9]+\.' | head -1)
export KAGENT_LOCAL_HOST=$HOST_IP
export KAGENT_URL="http://$(kubectl get svc -n kagent kagent-controller -o jsonpath='{.status.loadBalancer.ingress[0].ip}'):8083"
export KAGENT_GRPC_URL="$(kubectl get svc -n kagent kagent-controller -o jsonpath='{.status.loadBalancer.ingress[0].ip}'):8084"

# Run critical tests with namespace-scoped RBAC to verify the controller didn't lose needed permissions
cd go
Expand Down Expand Up @@ -169,6 +172,25 @@ jobs:
- name: Previous-release queries vs current schema
run: make -C go check-query-contraction

proto-check:
name: Protobuf Contract Check
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v7
with:
go-version-file: go/go.mod
cache: true
cache-dependency-path: go/go.sum
- name: Lint and generate protobuf contracts
run: make proto-check
- name: Check protobuf compatibility
run: make proto-breaking

upgrade-tests:
needs:
- setup
Expand Down
56 changes: 46 additions & 10 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ HELM_REPO ?= oci://ghcr.io/kagent-dev
HELM_DIST_FOLDER ?= dist

BUILD_DATE := $(shell date -u '+%Y-%m-%d')
LOCAL_BUILD_ID ?= $(shell date -u '+%Y%m%d%H%M%S')
GIT_COMMIT := $(shell git rev-parse --short HEAD || echo "unknown")
VERSION ?= $(shell git describe --tags --always 2>/dev/null | grep v || echo "v0.0.0-$(GIT_COMMIT)")

Expand Down Expand Up @@ -99,6 +100,8 @@ LDFLAGS := -X github.com/$(DOCKER_REPO)/go/core/internal/version.Version=$(VERSI
TOOLS_UV_VERSION ?= 0.10.4
TOOLS_NODE_VERSION ?= 24
TOOLS_PYTHON_VERSION ?= 3.13
BUF_VERSION ?= v1.72.0
BUF := go run github.com/bufbuild/buf/cmd/buf@$(BUF_VERSION)

# build args
TOOLS_IMAGE_BUILD_ARGS = --build-arg VERSION=$(VERSION)
Expand Down Expand Up @@ -138,6 +141,35 @@ print-tools-versions: ## Print tools versions
@echo "Tools Istio : $(TOOLS_ISTIO_VERSION)"
@echo "Tools Argo CD: $(TOOLS_ARGO_CD_VERSION)"

##@ Protobuf

.PHONY: proto-generate
proto-generate: ## Generate Go, TypeScript, and Python protobuf clients and servers
cd proto && $(BUF) generate

.PHONY: proto-lint
proto-lint: ## Lint repository-owned protobuf schemas
cd proto && $(BUF) lint

.PHONY: proto-breaking
proto-breaking: ## Check protobuf compatibility against the target branch (default: main)
@if git cat-file -e "$(PROTO_BREAKING_BRANCH):proto/buf.yaml" 2>/dev/null; then \
$(BUF) breaking proto --against ".git#branch=$(PROTO_BREAKING_BRANCH),subdir=proto"; \
else \
echo "No protobuf module on $(PROTO_BREAKING_BRANCH); skipping first-release breaking check"; \
fi

PROTO_BREAKING_BRANCH ?= main
PROTO_GENERATED_PATHS := go/api/gen ui/src/generated python/packages/kagent-proto/src/kagent

.PHONY: proto-check
proto-check: proto-lint proto-generate ## Regenerate protobuf artifacts and fail when committed output drifts
@if test -n "$$(git status --porcelain -- $(PROTO_GENERATED_PATHS))"; then \
echo "Generated protobuf files are out of date:"; \
git status --short -- $(PROTO_GENERATED_PATHS); \
exit 1; \
fi

##@ Git

.PHONY: init-git-hooks
Expand Down Expand Up @@ -207,7 +239,7 @@ endif
.PHONY: build-all
build-all: ## Build all images for amd64+arm64 without pushing (outputs to /dev/null for CI validation)
build-all: BUILD_ARGS ?= --progress=plain --builder $(BUILDX_BUILDER_NAME) --platform linux/amd64,linux/arm64 --output type=tar,dest=/dev/null
build-all: buildx-create
build-all: proto-generate buildx-create
$(DOCKER_BUILDER) $(BUILD_ARGS) $(TOOLS_IMAGE_BUILD_ARGS) -f go/Dockerfile ./go
$(DOCKER_BUILDER) $(BUILD_ARGS) $(TOOLS_IMAGE_BUILD_ARGS) -f go/Dockerfile.full ./go
$(DOCKER_BUILDER) $(BUILD_ARGS) $(TOOLS_IMAGE_BUILD_ARGS) -f ui/Dockerfile ./ui
Expand Down Expand Up @@ -238,10 +270,12 @@ endif

.PHONY: build-cli
build-cli: ## Build the kagent CLI (cross-compiled via go sub-make)
build-cli: proto-generate
make -C go build

.PHONY: build-cli-local
build-cli-local: ## Build the kagent CLI binary for the local machine
build-cli-local: proto-generate
make -C go clean
make -C go core/bin/kagent-local

Expand All @@ -268,7 +302,7 @@ controller-manifests: ## Regenerate CRD manifests and copy them into the Helm ch

.PHONY: build-controller
build-controller: ## Build and push the controller image (embeds agent runtime + acp-sandbox digests via scripts/controller-digest-ldflags.sh)
build-controller: buildx-create controller-manifests build-app build-app-full build-golang-adk build-golang-adk-full build-acp-sandbox-openclaw build-acp-sandbox-hermes
build-controller: proto-generate buildx-create controller-manifests build-app build-app-full build-golang-adk build-golang-adk-full build-acp-sandbox-openclaw build-acp-sandbox-hermes
@set -e; \
DIGEST_LDFLAGS=$$(CONTAINER_RUNTIME=$(CONTAINER_RUNTIME) \
APP_IMG=$(APP_IMG) \
Expand All @@ -286,13 +320,13 @@ build-controller: buildx-create controller-manifests build-app build-app-full bu

.PHONY: build-ui
build-ui: ## Build and push the UI image
build-ui: buildx-create
build-ui: proto-generate buildx-create
$(DOCKER_BUILDER) $(DOCKER_BUILD_ARGS) $(TOOLS_IMAGE_BUILD_ARGS) -t $(UI_IMG) -f ui/Dockerfile ./ui
$(DOCKER_PUSH) $(UI_IMG)

.PHONY: build-kagent-adk
build-kagent-adk: ## Build and push the Python kagent ADK image
build-kagent-adk: buildx-create
build-kagent-adk: proto-generate buildx-create
$(DOCKER_BUILDER) $(DOCKER_BUILD_ARGS) $(TOOLS_IMAGE_BUILD_ARGS) -t $(KAGENT_ADK_IMG) -f python/Dockerfile ./python
$(DOCKER_PUSH) $(KAGENT_ADK_IMG)

Expand All @@ -304,7 +338,7 @@ build-app: buildx-create build-kagent-adk

.PHONY: build-kagent-adk-full
build-kagent-adk-full: ## Build and push the full Python kagent ADK image (includes sandbox runtime)
build-kagent-adk-full: buildx-create
build-kagent-adk-full: proto-generate buildx-create
$(DOCKER_BUILDER) $(DOCKER_BUILD_ARGS) $(TOOLS_IMAGE_BUILD_ARGS) -t $(KAGENT_ADK_FULL_IMG) -f python/Dockerfile.full ./python
$(DOCKER_PUSH) $(KAGENT_ADK_FULL_IMG)

Expand All @@ -316,13 +350,13 @@ build-app-full: buildx-create build-kagent-adk-full

.PHONY: build-golang-adk
build-golang-adk: ## Build and push the Go ADK image
build-golang-adk: buildx-create
build-golang-adk: proto-generate buildx-create
$(DOCKER_BUILDER) $(DOCKER_BUILD_ARGS) $(TOOLS_IMAGE_BUILD_ARGS) --build-arg BUILD_PACKAGE=adk/cmd/main.go -t $(GOLANG_ADK_IMG) -f go/Dockerfile ./go
$(DOCKER_PUSH) $(GOLANG_ADK_IMG)

.PHONY: build-golang-adk-full
build-golang-adk-full: ## Build and push the Go ADK full image (with extra tooling)
build-golang-adk-full: buildx-create
build-golang-adk-full: proto-generate buildx-create
$(DOCKER_BUILDER) $(DOCKER_BUILD_ARGS) $(TOOLS_IMAGE_BUILD_ARGS) --build-arg BUILD_PACKAGE=adk/cmd/main.go -t $(GOLANG_ADK_FULL_IMG) -f go/Dockerfile.full ./go
$(DOCKER_PUSH) $(GOLANG_ADK_FULL_IMG)

Expand Down Expand Up @@ -397,8 +431,8 @@ push-test-skill: buildx-create ## Build and push E2E test skill images to the lo
.PHONY: create-kind-cluster

create-kind-cluster: ## Create a local kind cluster with MetalLB
CONTAINER_RUNTIME=$(CONTAINER_RUNTIME) bash ./scripts/kind/setup-kind.sh
CONTAINER_RUNTIME=$(CONTAINER_RUNTIME) bash ./scripts/kind/setup-metallb.sh
CONTAINER_RUNTIME=$(CONTAINER_RUNTIME) KIND_CLUSTER_NAME=$(KIND_CLUSTER_NAME) KIND_IMAGE_VERSION=$(KIND_IMAGE_VERSION) bash ./scripts/kind/setup-kind.sh
CONTAINER_RUNTIME=$(CONTAINER_RUNTIME) KIND_CLUSTER_NAME=$(KIND_CLUSTER_NAME) bash ./scripts/kind/setup-metallb.sh

.PHONY: use-kind-cluster
use-kind-cluster: ## Merge kind kubeconfig and set kagent as the default namespace
Expand Down Expand Up @@ -508,10 +542,12 @@ helm-install-provider: helm-version check-api-key
--set database.postgres.bundled.image.name=pgvector \
--set database.postgres.bundled.image.tag=pg18-trixie \
--set database.postgres.vectorEnabled=true \
$(KAGENT_HELM_EXTRA_ARGS)
$(KAGENT_HELM_EXTRA_ARGS) \
$(KAGENT_HELM_REDEPLOY_ARGS)

.PHONY: helm-install
helm-install: ## Build all images then install kagent onto the kind cluster
helm-install: KAGENT_HELM_REDEPLOY_ARGS = --set-string 'controller.podAnnotations.kagent\.dev/local-build-id=$(LOCAL_BUILD_ID)' --set-string 'ui.podAnnotations.kagent\.dev/local-build-id=$(LOCAL_BUILD_ID)'
helm-install: build
helm-install: helm-install-provider

Expand Down
65 changes: 37 additions & 28 deletions go/adk/cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package main
import (
"context"
"flag"
"net/http"
"os"
"strings"
"time"
Expand All @@ -15,6 +14,7 @@ import (
"github.com/kagent-dev/kagent/go/adk/pkg/app"
"github.com/kagent-dev/kagent/go/adk/pkg/auth"
"github.com/kagent-dev/kagent/go/adk/pkg/config"
"github.com/kagent-dev/kagent/go/adk/pkg/controllerclient"
kagentmemory "github.com/kagent-dev/kagent/go/adk/pkg/memory"
runnerpkg "github.com/kagent-dev/kagent/go/adk/pkg/runner"
"github.com/kagent-dev/kagent/go/adk/pkg/session"
Expand Down Expand Up @@ -79,7 +79,7 @@ func main() {
configDir = "/config"
}

kagentURL := os.Getenv("KAGENT_URL")
kagentGRPCURL := os.Getenv("KAGENT_GRPC_URL")

if err := config.MaterializeFromEnv(configDir); err != nil {
logger.Error(err, "Failed to materialize agent config from environment", "configDir", configDir)
Expand Down Expand Up @@ -132,27 +132,38 @@ func main() {
logger.Info("ADK telemetry disabled (set OTEL_TRACING_ENABLED or OTEL_LOGGING_ENABLED to true)")
}

// Create authenticated HTTP client when kagent persistence is enabled.
// This client is shared between the executor's session service and
// app.New's task store, avoiding duplicate token services.
var httpClient *http.Client
// Create one authenticated controller channel for all kagent persistence.
var controllerClient *controllerclient.Client
var tokenService *auth.KAgentTokenService
if kagentURL != "" {
if kagentGRPCURL != "" {
tokenService = auth.NewKAgentTokenService(appName)
if err := tokenService.Start(context.Background()); err != nil {
logger.Error(err, "Failed to start token service")
} else {
logger.Info("Token service started")
}
defer tokenService.Stop()
httpClient = auth.NewHTTPClientWithToken(tokenService)
controllerClient, err = controllerclient.New(controllerclient.Config{
Target: kagentGRPCURL,
AgentName: appName,
TokenProvider: tokenService,
})
if err != nil {
logger.Error(err, "Failed to create controller gRPC client", "target", kagentGRPCURL)
os.Exit(1)
}
defer func() {
if err := controllerClient.Close(); err != nil {
logger.Error(err, "Failed to close controller gRPC client")
}
}()
}

// The executor needs a session service for its BeforeExecute callback
// (session creation/lookup). This must be created before the executor.
// AgentConfig.session_db_url (set by the controller for durable-dir substrate sandbox
// agents) selects the local store; otherwise sessions live in the controller database.
sessionService, err := session.NewService(agentConfig.SessionDBURL, kagentURL, httpClient)
sessionService, err := session.NewService(agentConfig.SessionDBURL, controllerClient)
if err != nil {
logger.Error(err, "Failed to open local session store", "url", agentConfig.SessionDBURL)
os.Exit(1)
Expand All @@ -161,22 +172,21 @@ func main() {
case *session.LocalSessionService:
logger.Info("Using local durable-dir session store", "url", agentConfig.SessionDBURL)
case *session.KAgentSessionService:
logger.Info("Using KAgent session service", "url", kagentURL)
logger.Info("Using KAgent gRPC session service", "target", kagentGRPCURL)
default:
logger.Info("No KAGENT_URL set, using in-memory session and no task persistence")
logger.Info("No KAGENT_GRPC_URL set, using in-memory session and no task persistence")
}

ctx := logr.NewContext(context.Background(), logger)

// Build memory service if configured.
var memoryService *kagentmemory.KagentMemoryService
if agentConfig.Memory != nil && kagentURL != "" {
if agentConfig.Memory != nil && controllerClient != nil {
memSvc, err := kagentmemory.New(kagentmemory.Config{
AgentName: appName,
APIURL: kagentURL,
HTTPClient: httpClient,
TTLDays: agentConfig.Memory.TTLDays,
EmbeddingConfig: agentConfig.Memory.Embedding,
AgentName: appName,
ControllerClient: controllerClient,
TTLDays: agentConfig.Memory.TTLDays,
EmbeddingConfig: agentConfig.Memory.Embedding,
})
if err != nil {
logger.Error(err, "Failed to create memory service")
Expand All @@ -186,7 +196,7 @@ func main() {
logger.Info("Memory service enabled", "appName", appName)
}

runnerConfig, err := runnerpkg.CreateRunnerConfig(ctx, agentConfig, sessionService, appName, memoryService, kagentURL, httpClient)
runnerConfig, err := runnerpkg.CreateRunnerConfig(ctx, agentConfig, sessionService, appName, memoryService, controllerClient)
if err != nil {
logger.Error(err, "Failed to create Google ADK Runner config")
os.Exit(1)
Expand Down Expand Up @@ -215,17 +225,16 @@ func main() {
}

// Delegate server, task store, and remaining infrastructure to app.New.
// Passing HTTPClient prevents app.New from creating a second token service.
kagentApp, err := app.New(app.AppConfig{
AgentCard: *agentCard,
Host: *host,
Port: port,
KAgentURL: kagentURL,
AppName: appName,
ShutdownTimeout: 5 * time.Second,
Logger: logger,
HTTPClient: httpClient,
Agent: runnerConfig.Agent,
AgentCard: *agentCard,
Host: *host,
Port: port,
KAgentGRPCURL: kagentGRPCURL,
AppName: appName,
ShutdownTimeout: 5 * time.Second,
Logger: logger,
ControllerClient: controllerClient,
Agent: runnerConfig.Agent,
}, executor)
if err != nil {
logger.Error(err, "Failed to create app")
Expand Down
6 changes: 3 additions & 3 deletions go/adk/examples/byo/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
// The app builder automatically wires kagent infrastructure based on
// environment variables:
//
// - KAGENT_URL: when set, enables remote session and task persistence via
// the kagent controller API. Token auth is handled automatically.
// - KAGENT_GRPC_URL: when set, enables remote session and task persistence via
// the kagent controller gRPC API. Token auth is handled automatically.
// - KAGENT_NAMESPACE / KAGENT_NAME: used to derive the app name for session
// scoping. Falls back to the agent card name.
// - PORT: the port to listen on (default "8080").
Expand All @@ -30,7 +30,7 @@
//
// Run with kagent persistence:
//
// KAGENT_URL=http://kagent-controller:8080 OPENAI_API_KEY=sk-... go run ./examples/byo/
// KAGENT_GRPC_URL=kagent-controller:8084 OPENAI_API_KEY=sk-... go run ./examples/byo/
//
// Test with curl:
//
Expand Down
Loading
Loading