Skip to content

Commit 3bce2e6

Browse files
markturanskyclaude
andauthored
feat: ambient control plane with gRPC runner integration (#975)
## Summary - **Control Plane**: New `ambient-control-plane` Go service that watches the ambient-api-server via gRPC streams and reconciles desired state into Kubernetes (sessions → Jobs, projects → Namespaces/RoleBindings). Supports `kube`, `local`, and `test` modes. - **Runner**: gRPC-based AG-UI event streaming for the Python runner — `GRPCSessionListener` watches inbound session messages, `GRPCMessageWriter` pushes structured AG-UI events back, with full structured logging and observability. - **Manifests**: RBAC, gRPC Service/Route, kind/production overlays, and CI image build for the control plane. ## Components changed < /dev/null | Component | Change | |---|---| | `components/ambient-control-plane/` | New Go service (informer, reconciler, kubeclient, watcher) | | `components/runners/ambient-runner/` | gRPC transport layer (`grpc_transport.py`, `_grpc_client.py`, `_session_messages_api.py`) | | `components/manifests/` | RBAC, gRPC route, kind overlay patches, CI workflow | ## Test plan - [x] `go fmt`, `go vet`, `golangci-lint` — all clean - [x] `go test ./...` — all packages pass - [x] `ruff format` + `ruff check` — all clean - [x] `python -m pytest tests/` — 70 tests pass (3 test files; 2 pre-existing hang unrelated to this PR) - [x] Images built and loaded into running kind cluster - [x] `deployment/ambient-control-plane` rolled out successfully 🤖 Generated with [Claude Code](https://claude.ai/code) --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent cdf2f6f commit 3bce2e6

72 files changed

Lines changed: 10535 additions & 167 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
name: Control Plane Unit Tests
2+
3+
on:
4+
push:
5+
branches: [main]
6+
paths:
7+
- 'components/ambient-control-plane/**'
8+
- 'components/ambient-sdk/go-sdk/**'
9+
- '.github/workflows/ambient-control-plane-tests.yml'
10+
pull_request:
11+
branches: [main]
12+
paths:
13+
- 'components/ambient-control-plane/**'
14+
- 'components/ambient-sdk/go-sdk/**'
15+
- '.github/workflows/ambient-control-plane-tests.yml'
16+
workflow_dispatch:
17+
18+
permissions:
19+
contents: read
20+
21+
concurrency:
22+
group: cp-tests-${{ github.event.pull_request.number || github.ref }}
23+
cancel-in-progress: true
24+
25+
jobs:
26+
unit-tests:
27+
name: Unit Tests
28+
runs-on: ubuntu-latest
29+
timeout-minutes: 10
30+
31+
steps:
32+
- name: Checkout code
33+
uses: actions/checkout@v6
34+
35+
- name: Set up Go
36+
uses: actions/setup-go@v6
37+
with:
38+
go-version-file: 'components/ambient-control-plane/go.mod'
39+
cache-dependency-path: 'components/ambient-control-plane/go.sum'
40+
41+
- name: Run tests
42+
working-directory: components/ambient-control-plane
43+
run: go test -v -count=1 -race ./...
44+
45+
- name: Build binary
46+
working-directory: components/ambient-control-plane
47+
run: go build -o /dev/null ./cmd/ambient-control-plane

Makefile

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
.PHONY: help setup build-all build-frontend build-backend build-operator build-runner build-state-sync build-public-api build-cli deploy clean check-architecture
1+
.PHONY: help setup build-all build-frontend build-backend build-operator build-runner build-state-sync build-public-api build-control-plane build-cli deploy clean check-architecture deploy-openshift
22
.PHONY: local-up local-down local-clean local-status local-rebuild local-reload-backend local-reload-frontend local-reload-operator local-reload-api-server local-sync-version
33
.PHONY: local-dev-token
44
.PHONY: local-logs local-logs-backend local-logs-frontend local-logs-operator local-shell local-shell-frontend
@@ -65,6 +65,7 @@ RUNNER_IMAGE ?= vteam_claude_runner:$(IMAGE_TAG)
6565
STATE_SYNC_IMAGE ?= vteam_state_sync:$(IMAGE_TAG)
6666
PUBLIC_API_IMAGE ?= vteam_public_api:$(IMAGE_TAG)
6767
API_SERVER_IMAGE ?= vteam_api_server:$(IMAGE_TAG)
68+
CONTROL_PLANE_IMAGE ?= ambient_control_plane:$(IMAGE_TAG)
6869

6970
# Podman prefixes image names with localhost/ — kind load needs to use the same
7071
# name so containerd can match the image reference used in the deployment spec
@@ -100,6 +101,7 @@ KIND_HOST ?=
100101
# Vertex AI Configuration (for LOCAL_VERTEX=true)
101102
# These inherit from environment if set, or can be overridden on command line
102103
LOCAL_IMAGES ?= false
104+
LOCAL_RUNNER ?= false
103105
LOCAL_VERTEX ?= false
104106
ANTHROPIC_VERTEX_PROJECT_ID ?= $(shell echo $$ANTHROPIC_VERTEX_PROJECT_ID)
105107
CLOUD_ML_REGION ?= $(shell echo $$CLOUD_ML_REGION)
@@ -160,7 +162,7 @@ help: ## Display this help message
160162

161163
##@ Building
162164

163-
build-all: build-frontend build-backend build-operator build-runner build-state-sync build-public-api build-api-server ## Build all container images
165+
build-all: build-frontend build-backend build-operator build-runner build-state-sync build-public-api build-api-server build-control-plane ## Build all container images
164166

165167
build-frontend: ## Build frontend image
166168
@echo "$(COLOR_BLUE)$(COLOR_RESET) Building frontend with $(CONTAINER_ENGINE)..."
@@ -204,6 +206,14 @@ build-api-server: ## Build ambient API server image
204206
-t $(API_SERVER_IMAGE) .
205207
@echo "$(COLOR_GREEN)$(COLOR_RESET) API server built: $(API_SERVER_IMAGE)"
206208

209+
build-control-plane: ## Build ambient-control-plane image
210+
@echo "$(COLOR_BLUE)$(COLOR_RESET) Building ambient-control-plane with $(CONTAINER_ENGINE)..."
211+
@$(CONTAINER_ENGINE) build $(PLATFORM_FLAG) $(BUILD_FLAGS) \
212+
-t $(CONTROL_PLANE_IMAGE) \
213+
-f components/ambient-control-plane/Dockerfile \
214+
components/
215+
@echo "$(COLOR_GREEN)$(COLOR_RESET) Control plane built: $(CONTROL_PLANE_IMAGE)"
216+
207217
build-cli: ## Build acpctl CLI binary
208218
@echo "$(COLOR_BLUE)$(COLOR_RESET) Building acpctl CLI..."
209219
@cd components/ambient-cli && make build
@@ -248,13 +258,56 @@ registry-login: ## Login to container registry
248258

249259
push-all: registry-login ## Push all images to registry
250260
@echo "$(COLOR_BLUE)$(COLOR_RESET) Pushing images to $(REGISTRY)..."
251-
@for image in $(FRONTEND_IMAGE) $(BACKEND_IMAGE) $(OPERATOR_IMAGE) $(RUNNER_IMAGE) $(STATE_SYNC_IMAGE) $(PUBLIC_API_IMAGE) $(API_SERVER_IMAGE); do \
261+
@for image in $(FRONTEND_IMAGE) $(BACKEND_IMAGE) $(OPERATOR_IMAGE) $(RUNNER_IMAGE) $(STATE_SYNC_IMAGE) $(PUBLIC_API_IMAGE) $(API_SERVER_IMAGE) $(CONTROL_PLANE_IMAGE); do \
252262
echo " Tagging and pushing $$image..."; \
253263
$(CONTAINER_ENGINE) tag $$image $(REGISTRY)/$$image && \
254264
$(CONTAINER_ENGINE) push $(REGISTRY)/$$image; \
255265
done
256266
@echo "$(COLOR_GREEN)$(COLOR_RESET) All images pushed"
257267

268+
deploy-openshift: ## Deploy to OpenShift cluster. Use LOCAL_RUNNER=true to build+push runner from source to internal registry.
269+
@echo "$(COLOR_BOLD)Deploying to OpenShift cluster$(COLOR_RESET)"
270+
@oc whoami >/dev/null 2>&1 || (echo "$(COLOR_RED)$(COLOR_RESET) Not logged in to OpenShift. Run: oc login" && exit 1)
271+
@command -v ocm >/dev/null 2>&1 || (echo "$(COLOR_RED)$(COLOR_RESET) ocm CLI not found. Install from https://console.redhat.com/openshift/downloads and run: ocm login" && exit 1)
272+
@ocm token >/dev/null 2>&1 || (echo "$(COLOR_RED)$(COLOR_RESET) ocm token unavailable. Run: ocm login" && exit 1)
273+
@REGISTRY_HOST=$$(oc get route default-route -n openshift-image-registry --template='{{ .spec.host }}' 2>/dev/null) && \
274+
INTERNAL_REG="image-registry.openshift-image-registry.svc:5000/ambient-code" && \
275+
INTERNAL_RUNNER="$$INTERNAL_REG/vteam_claude_runner:latest" && \
276+
echo "$(COLOR_BLUE)$(COLOR_RESET) Registry: $$REGISTRY_HOST" && \
277+
oc whoami -t | $(CONTAINER_ENGINE) login --tls-verify=false -u kubeadmin --password-stdin "$$REGISTRY_HOST" && \
278+
echo "$(COLOR_BLUE)$(COLOR_RESET) Building and pushing control plane..." && \
279+
$(MAKE) --no-print-directory build-control-plane && \
280+
$(CONTAINER_ENGINE) tag $(CONTROL_PLANE_IMAGE) $$REGISTRY_HOST/ambient-code/$(CONTROL_PLANE_IMAGE) && \
281+
$(CONTAINER_ENGINE) push --tls-verify=false $$REGISTRY_HOST/ambient-code/$(CONTROL_PLANE_IMAGE) && \
282+
echo "$(COLOR_GREEN)$(COLOR_RESET) Pushed $(CONTROL_PLANE_IMAGE)" && \
283+
if [ "$(LOCAL_RUNNER)" = "true" ]; then \
284+
echo "$(COLOR_BLUE)$(COLOR_RESET) LOCAL_RUNNER=true: building and pushing runner from source..." && \
285+
$(MAKE) --no-print-directory build-runner && \
286+
$(CONTAINER_ENGINE) tag $(RUNNER_IMAGE) $$REGISTRY_HOST/ambient-code/$(RUNNER_IMAGE) && \
287+
$(CONTAINER_ENGINE) push --tls-verify=false $$REGISTRY_HOST/ambient-code/$(RUNNER_IMAGE) && \
288+
echo "$(COLOR_GREEN)$(COLOR_RESET) Pushed $(RUNNER_IMAGE)"; \
289+
else \
290+
echo "$(COLOR_BLUE)$(COLOR_RESET) Using quay.io runner image (pass LOCAL_RUNNER=true to build from source)"; \
291+
INTERNAL_RUNNER="quay.io/ambient_code/vteam_claude_runner:latest"; \
292+
fi && \
293+
echo "$(COLOR_BLUE)$(COLOR_RESET) Applying production manifests..." && \
294+
kubectl kustomize components/manifests/overlays/production/ | kubectl apply --validate=false -f - && \
295+
echo "$(COLOR_BLUE)$(COLOR_RESET) Patching runner image to: $$INTERNAL_RUNNER" && \
296+
kubectl set env deployment/ambient-control-plane -n ambient-code RUNNER_IMAGE="$$INTERNAL_RUNNER" && \
297+
REGISTRY_JSON=$$(oc get configmap ambient-agent-registry -n ambient-code -o jsonpath='{.data.agent-registry\.json}') && \
298+
UPDATED_JSON=$$(echo "$$REGISTRY_JSON" | sed "s|quay\.io/ambient_code/vteam_claude_runner:[^\"]*|$$INTERNAL_RUNNER|g") && \
299+
kubectl patch configmap ambient-agent-registry -n ambient-code --type=merge \
300+
-p "{\"data\":{\"agent-registry.json\":$$(echo "$$UPDATED_JSON" | python3 -c 'import sys,json; print(json.dumps(sys.stdin.read()))')}}" && \
301+
echo "$(COLOR_GREEN)$(COLOR_RESET) Agent registry updated" && \
302+
echo "$(COLOR_BLUE)$(COLOR_RESET) Provisioning control plane RH SSO token..." && \
303+
oc delete secret ambient-control-plane-token -n ambient-code 2>/dev/null || true && \
304+
oc create secret generic ambient-control-plane-token -n ambient-code \
305+
--from-literal=token="$$(ocm token)" && \
306+
echo "$(COLOR_BLUE)$(COLOR_RESET) Restarting control-plane deployment..." && \
307+
oc rollout restart deployment/ambient-control-plane -n ambient-code && \
308+
oc rollout status deployment/ambient-control-plane -n ambient-code --timeout=120s && \
309+
echo "$(COLOR_GREEN)$(COLOR_RESET) OpenShift deployment complete"
310+
258311
##@ MinIO S3 Storage
259312

260313
setup-minio: ## Set up MinIO and create initial bucket
@@ -948,7 +1001,7 @@ check-architecture: ## Validate build architecture matches host
9481001

9491002
_kind-load-images: ## Internal: Load images into kind cluster
9501003
@echo "$(COLOR_BLUE)$(COLOR_RESET) Loading images into kind ($(KIND_CLUSTER_NAME))..."
951-
@for img in $(BACKEND_IMAGE) $(FRONTEND_IMAGE) $(OPERATOR_IMAGE) $(RUNNER_IMAGE) $(STATE_SYNC_IMAGE) $(PUBLIC_API_IMAGE) $(API_SERVER_IMAGE); do \
1004+
@for img in $(BACKEND_IMAGE) $(FRONTEND_IMAGE) $(OPERATOR_IMAGE) $(RUNNER_IMAGE) $(STATE_SYNC_IMAGE) $(PUBLIC_API_IMAGE) $(API_SERVER_IMAGE) $(CONTROL_PLANE_IMAGE); do \
9521005
echo " Loading $(KIND_IMAGE_PREFIX)$$img..."; \
9531006
if [ -n "$(KIND_HOST)" ] || [ "$(CONTAINER_ENGINE)" = "podman" ]; then \
9541007
$(CONTAINER_ENGINE) save $(KIND_IMAGE_PREFIX)$$img | \

0 commit comments

Comments
 (0)