Split Control/Data Plane deployment
Last updated:
Split deployment runs management and scheduling in Control Plane and task execution in Data Plane. Use it when execution environments need separate maintenance or additional nodes. For a trial on one host, Standalone is simpler to configure.
This page assumes familiarity with containers, databases, and reverse proxies. Prepare shared PostgreSQL, Data Protection keys for decrypting credentials, and workspaces accessible to every execution node. The entry proxy must support WebSocket for conversation events.
Roles
| Host | Responsibility |
|---|---|
| Control Plane | Setup, Web, management APIs, Job scheduling |
| Data Plane | SignalR Execution, A2A, durable execution workers |
| Standalone | Both roles combined for a single-server setup |
Split deployment uses Distributed execution. In this mode, turns that run an External Agent (Claude Code, Codex, or Pi) directly in Chat fail with “Distributed execution currently supports System Agents only.” To use those external agents directly, choose Standalone with InProcess execution.
Split deployments require PostgreSQL for the database and locks, plus Distributed execution on both roles. SQLite or in-memory locks cannot replace cross-node coordination.
This is an environment configuration fragment for both roles. An empty lock connection string reuses the database connection. Supply the real database connection string through Secrets.
Startup and routing
- Configure the database, both Hosts, shared keys, and directories using the cluster Compose reference.
- Start Control Plane first, initialize it, and confirm readiness:
GET /api/health/readyreturns 503 until the Host is initialized and can reach its database, then 200;GET /api/health/liveonly shows that the process is running. Neither requires sign-in. - Start Data Plane, then add replicas as needed.
- Route
/api/hubs/exec,/a2a/*, and/.well-known/agents.jsonto Data Plane; route other application paths to Control Plane.
Preserve Host, authentication headers/Cookies, and WebSocket Upgrade. Exclude execution Hub query strings from proxy access logs. Control Plane does not serve A2A.
Reading the examples
For Docker Compose, start with the cluster Compose file linked below and follow the startup order and routes above. For Kubernetes, the next section uses kind, which runs a local Kubernetes cluster in containers. Pods run applications, Services provide access addresses, and PVs/PVCs declare and request storage.
Both approaches need a shared client entry point. The Nginx section shows which requests go to each role. Verify the services and database first, then routing, to distinguish service failures from proxy failures.
Kubernetes YAML examples
The repository’s deploy/k8s directory provides a local, single-node kind example. It uses separate Control Plane and Data Plane Deployments, an external PostgreSQL database, and NodePorts that can connect to the Nginx configuration below. These files do not create PostgreSQL or an Ingress Controller.
| File | Purpose |
|---|---|
| kind-agw-cluster.yaml | Create the local kind cluster with port and directory mappings |
| agw-data-pv-pvc.yaml | Provide a data volume shared by Pods on the same node |
| agw-control-plane-deployment.yaml | One Control Plane replica and a NodePort Service |
| agw-data-plane-deployment.yaml | Two Data Plane replicas and a NodePort Service |
Cluster entry points and shared directory
kind-agw-cluster.yaml maps both NodePorts to the host loopback address and mounts /opt/agw into the kind node:
Prepare /opt/agw/agw-data on the container runtime host before creating the cluster. With a Docker/Podman VM, configure file sharing so the directory is available inside the VM. The control-plane node role here belongs to Kubernetes; it is distinct from AGW’s Control Plane service.
The storage path is:
The corresponding PV/PVC follows. Retain preserves reclaimed volume data but does not replace backups. The PVC requests 1Gi, while the PV declares 5Gi capacity.
ReadWriteOnce permits multiple Pods on the same node to mount the volume, covering both roles and Data Plane replicas in this example. hostPath is not shared storage across nodes. For multiple nodes, use shared storage supported by your cluster and keep keys, credentials, and Project workspace paths consistent across execution nodes. Add mounts for workspaces located outside /data.
Data Plane Deployment and Service
This is the repository’s complete Data Plane example. It runs two replicas on container port 8080, reads the agw-database Secret, and exposes Service port 30820. The Control Plane file uses the same volume and database settings, with one replica and NodePort 30816. It also reads password from agw-admin as Setup__AdminPassword.
Before using it, check:
- Images:
localhost/agw-…:localwithimagePullPolicy: Neverrequires images to be built and loaded into kind first. For registry images, use reachable image addresses and versions, with an appropriate pull policy and credentials. - Database: both roles must use the same PostgreSQL database reachable from the Pods.
localhostin a connection string refers to the Pod itself, usually not the host database. - Permissions: the local example runs as root to accommodate its directory permissions. Set an appropriate UID/GID for your storage in other environments instead of copying this local setting unchanged.
- Connection affinity: the Service uses
ClientIPaffinity to help SignalR requests reach the same Pod. With Nginx outside the cluster, multiple clients may appear as one proxy IP, so this does not guarantee even load distribution.
Deployment order
Prepare the local images, data directory, and files containing the two Secret values, then run from the repository root. Secret files should contain only the relevant values; keep real credentials out of Git. These commands use the default namespace of the current kubectl context. If you choose another namespace, keep Deployments, Services, PVCs, and Secrets together.
rollout status confirms the Deployment rollout, not application initialization. In this example, Control Plane’s Setup__AdminPassword triggers first-run initialization. Check logs and the sign-in page before starting Data Plane. Existing database authentication settings are not overwritten by this initial password.
With the kind mappings above, the upstreams in the Nginx example below can use 127.0.0.1:30816 and 127.0.0.1:30820. Nginx inside the cluster can instead use agw-control-plane:30816 and agw-data-plane:30820 in the same namespace. After checking that the PVC is Bound and Pods are running, verify login, execution connections, and which nodes receive requests.
Do not run kubectl apply -f deploy/k8s/: the kind Cluster file is input to kind, not a Kubernetes API resource. Changing kind port or mount mappings requires recreating the cluster; back up data first. See the local kind deployment guide for the complete source instructions.
Nginx configuration example
In this example, Nginx provides one entry point, Control Plane listens on 30816, and Data Plane on 30820, matching the kind example above; the repository’s deploy/nginx.split.conf.example and cluster Compose example use 30817 for Data Plane. Replace these example ports with the actual Host listeners. For separate hosts or containers, replace 127.0.0.1 with addresses reachable from Nginx.
Web hosted by Control Plane
Save this as a site configuration included from the http {} block in nginx.conf. map, log_format, and upstream must not be nested inside server {}. Log paths are relative to the Nginx prefix; create the directories or use writable absolute paths.
The example uses HTTP for local verification. For external access, configure listen 443 ssl;, ssl_certificate, and ssl_certificate_key in this server, using your domain and valid certificate, and redirect HTTP to HTTPS.
| Request | Destination | Purpose |
|---|---|---|
/api/hubs/exec and child paths | Data Plane | SignalR negotiation and execution connections |
/a2a/* | Data Plane | A2A requests and streaming responses |
/.well-known/agents.json | Data Plane | Agent discovery |
| Other paths | Control Plane | Setup, management APIs, OpenAPI, Web pages, and static assets |
The proxy_pass directives have no URI suffix, preserving the original path and query parameters. Authentication headers and Cookies are forwarded by default. Upgrade headers and HTTP/1.1 support WebSockets. Disabling response buffering on execution and A2A routes lets streaming output reach clients promptly. The 3600s values are proxy read/write timeouts, not a guarantee of uninterrupted tasks of any duration.
With multiple Data Plane instances, ip_hash aims to keep requests from one IP on the same instance so SignalR negotiation and subsequent connections reach the same node. It does not replace shared database, execution-state, and recovery configuration. If another proxy sits in front of Nginx, configure trusted proxies and client-IP handling for your network rather than trusting arbitrary X-Forwarded-For headers.
The access log uses $uri without query parameters. Its upstream field helps identify the receiving node. client_max_body_size controls only Nginx’s request limit; it does not increase AGW’s attachment limits.
A separate Web service
If Web runs separately on 3001, retain the Data Plane routes and common proxy settings above. Add the agw_web upstream and the management routes below, replacing the original location /. Port 3001 is the repository’s Web development port; use your Web service’s actual port in deployment.
Both /setup and /setup/ reach Control Plane, as do ordinary /api/ requests. The longer /api/hubs/exec match still reaches Data Plane. Configure the separate Web service’s own backend address to point to Control Plane. Clients should use the shared Nginx entry point.
Check and load the configuration
After saving your deployment configuration, validate it before reloading:
Run the reload in your own deployment environment. Verify login and page assets. In browser network tools, check for a successful WebSocket upgrade (101) on the execution connection and confirm its upstream is Data Plane in the access log. Management APIs should reach Control Plane. For login failures, check Cookies, forwarded scheme, and application proxy-trust settings. For execution connection failures, check Upgrade headers, routing, and the Data Plane port.
Verify
Test login, a Chat run, and a Job in order, inspecting the actual execution node. All Hosts read initialization and authentication from the same database. Recovery also requires consistent directories, keys, and runtime credentials; running containers alone do not prove readiness.