Skip to content
Closed
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
123 changes: 123 additions & 0 deletions demos/antigravity-cli-multiplex/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# Antigravity CLI Multiplex Demo

A demo of three Antigravity-driven agents sharing two Agent Substrate pods. Substrate suspends idle agents and resumes them on demand, so the cluster runs *fewer pods than agents*.

> [!NOTE]
> This demo intentionally provisions **two pods for three agents** to exercise substrate's suspend/resume path. The same pattern scales — ten agents on three pods, a hundred agents on twenty.

## What this shows

- Three Antigravity agents (`luna`, `mars`, `orion`) registered as Substrate actors.
- A `WorkerPool` of two pods.
- A small web UI that drives "give a task" against random idle agents and renders the queued/running/completed badge state per agent.
- Substrate handles the hard parts: state snapshot on suspend, scheduling decisions, resume-correctness when a pod becomes available.

## Audience

This guide assumes you know Kubernetes and the general shape of agent runtimes (autonomy + LLM API access). It does **not** assume prior Substrate experience.

## Prerequisites

- A Kubernetes cluster with **Agent Substrate** installed (`./hack/install-ate.sh` from this repo's root).
- `kubectl` configured against that cluster (the dashboard uses the operator's kubeconfig via [`client-go`](https://github.com/kubernetes/client-go) for pod-log reads).
- Network reach to the substrate **ateapi** gRPC service (`ateapi.ate-system:8080`). When running the dashboard from outside the cluster, port-forward it in a separate terminal and keep it running for the lifetime of the demo:
```bash
# Terminal 1: ateapi port-forward
kubectl port-forward svc/ateapi 8080:8080 -n ate-system
```
- Antigravity authentication configured for the command you run in the workload. The official Linux package is installed from Google's Antigravity apt repo.
- A GCS bucket for substrate state snapshots (configured during Substrate install).
- `KO_DOCKER_REPO` set to a registry you can push to (e.g. `gcr.io/${PROJECT_ID}/ate-images`, same as `hack/ate-dev-env.sh.example`). The deploy step builds and pushes the workload image there with a sha256-pinned reference.
- `docker buildx` and `jq` (the deploy function builds the workload image — a Dockerfile-based Antigravity wrapper, not a Go binary, so `ko` doesn't apply for the workload itself).

## Components

| Path | Purpose |
|---|---|
| `demos/antigravity-cli-multiplex/antigravity-cli-multiplex.yaml.tmpl` | Namespace, WorkerPool, ActorTemplates in a single envsubst template |
| `hack/install-demo-antigravity-cli-multiplex.sh` | Sourced by `install-ate.sh`; registers `--deploy-demo-antigravity-cli-multiplex` and `--delete-demo-antigravity-cli-multiplex` |
| `demos/antigravity-cli-multiplex/workload/` | The agent container image source (Dockerfile + entrypoint that wires Antigravity; built and pushed by the deploy step) |
| `demos/antigravity-cli-multiplex/ui/` | Static dashboard (`index.html` + `server.go`) that talks to the cluster |

## How to Run

### 1. Deploy the demo

From the repo root, with your substrate bucket name in the environment:

```bash
BUCKET_NAME=your-substrate-bucket \
./hack/install-ate.sh --deploy-demo-antigravity-cli-multiplex
```

This creates the `antigravity-cli-multiplex-demo` namespace, a 2-pod `WorkerPool`, and three `ActorTemplate` objects named `luna`, `mars`, `orion`. Under the hood, the deploy function builds the workload image with `docker buildx`, pushes it to `${KO_DOCKER_REPO}/antigravity-cli-multiplex-demo-workload`, resolves the pushed sha256 digest, and substitutes the digest-pinned reference plus `BUCKET_NAME` into the manifest template at apply time.

The workload executes `ANTIGRAVITY_COMMAND` once per tick. The default template command is intentionally explicit and easy to update because the public Antigravity package currently exposes the official `antigravity` binary, but does not document a single-prompt headless flag in the public docs. Override per-template by editing `ANTIGRAVITY_COMMAND` in `antigravity-cli-multiplex.yaml.tmpl` before deploying.

Check that everything is running as expected:

```bash
# k8s-native resources (these work with plain kubectl)
kubectl get pods,workerpool,actortemplate -n antigravity-cli-multiplex-demo

# Substrate-native (uses the kubectl-ate plugin against ateapi)
kubectl ate get actors
kubectl ate get workers
```

### 2. Start the dashboard

Make sure the ateapi port-forward from the [Prerequisites](#prerequisites) is still running, then:

```bash
cd demos/antigravity-cli-multiplex/ui
PORT=8090 ATEAPI_ADDR=localhost:8080 go run .
```

Or build a binary:

```bash
cd demos/antigravity-cli-multiplex/ui
go build -o ui-server .
PORT=8090 ATEAPI_ADDR=localhost:8080 ./ui-server
```

Either way, the UI is served on `http://localhost:8090` (or whatever `PORT` you pick — pick something that doesn't collide with the ateapi port-forward).

Env vars:

| Var | Default | Purpose |
|---|---|---|
| `PORT` | `8080` | TCP port the dashboard binds (pick `≠ ATEAPI_ADDR`'s port when both run on the same host). |
| `ATEAPI_ADDR` | `localhost:8080` | Address of the substrate ateapi gRPC service. |
| `DEMO_NAMESPACE` | `antigravity-cli-multiplex-demo` | Kubernetes namespace the dashboard filters to and reads pod logs from. |

`GET /healthz` reports whether the kube client picked up a cluster context (`logs:true|false`) — useful for quick smoke-tests after starting the server.

### 3. Drive the demo

Click "Give a task". The UI picks a random idle agent and creates a task for it. Watch:

- Badge flips to `queued` (the agent has work but isn't bound to a pod yet).
- Substrate finds a free pod and binds the agent. Badge flips to `running`.
- The agent runs the configured Antigravity command, writes a result, exits. Badge flips to `completed`.
- Substrate notices the inactivity and suspends the agent after a short idle window.
- The released pod becomes available for the next queued task on a different agent.

With three agents and two pods, the third agent stays suspended (state snapshotted) until a pod opens up.

## Upstream blockers worked around for this demo

Same upstream Substrate issues as the Claude Code variant. Each will be addressed by a separate upstream fix PR.

- **`#189`** — Atelet OCI bundle gaps (`Args`, `Secret`, symlinks).
- **`#197` Bug 2a** — `valueFrom.secretKeyRef` on `ActorTemplate` container env is not supported today. If your Antigravity command needs a token or API key, pass it as a plain `value:` env var until upstream support lands.
- **`#197` Bug 3** — Atelet symlink resolution.

## Teardown

```bash
./hack/install-ate.sh --delete-demo-antigravity-cli-multiplex
```

This removes the `antigravity-cli-multiplex-demo` namespace and all the resources created by the deploy step. You can also stop the port-forward and the dashboard processes in their respective terminals.
151 changes: 151 additions & 0 deletions demos/antigravity-cli-multiplex/antigravity-cli-multiplex.yaml.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
# 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.

# Three ActorTemplates share a 2-pod WorkerPool, so substrate must suspend
# at least one actor at any moment. If your Antigravity command needs a
# secret, pass it as a plain env var for now — substrate does not currently
# support `valueFrom.secretKeyRef` on ActorTemplate container env.
#
# WORKLOAD_IMAGE is the resolved sha256-digest reference for the
# antigravity-cli-multiplex-demo-workload image — built and pushed to
# ${KO_DOCKER_REPO} by `./hack/install-ate.sh --deploy-demo-antigravity-cli-multiplex`,
# substituted into this template at apply time.

apiVersion: v1
kind: Namespace
metadata:
name: antigravity-cli-multiplex-demo

---

# 2 worker replicas for 3 actors — the multiplex pressure that makes the
# substrate suspend/resume behavior visible.
apiVersion: ate.dev/v1alpha1
kind: WorkerPool
metadata:
name: antigravity-workerpool
namespace: antigravity-cli-multiplex-demo
spec:
replicas: 2
ateomImage: ko://github.com/agent-substrate/substrate/cmd/ateom-gvisor

---

# Three ActorTemplates, each running the same workload image with a
# distinct TASK prompt. Each template spawns one named actor, so there
# are 3 actors competing for 2 worker pods → substrate must suspend
# at least one at any moment.

apiVersion: ate.dev/v1alpha1
kind: ActorTemplate
metadata:
name: agent-luna
namespace: antigravity-cli-multiplex-demo
spec:
runsc:
amd64:
url: "gs://gvisor/releases/nightly/2026-05-19/x86_64/runsc"
sha256Hash: "a397be1abc2420d26bce6c70e6e2ff96c73aaaab929756c56f5e2089ea842b63"
arm64:
url: "gs://gvisor/releases/nightly/2026-05-19/aarch64/runsc"
sha256Hash: "1ba2366ae2efceba166046f51a4104f9261c9cb72c6db8f5b3fe2dc57dea86b9"
pauseImage: "registry.k8s.io/pause:3.10.2@sha256:f548e0e8e3dc1896ca956272154dde3314e8cc4fde0a57577ee9fa1c63f5baf4"
containers:
- name: antigravity
image: ${WORKLOAD_IMAGE}
command: ["/run.sh"]
env:
- name: ACTOR_NAME
value: "luna"
- name: TASK
value: "Tell me one short, surprising fact about the Moon. One sentence."
- name: INTERVAL_SECONDS
value: "45"
- name: ANTIGRAVITY_COMMAND
value: "antigravity --help"
workerPoolRef:
namespace: antigravity-cli-multiplex-demo
name: antigravity-workerpool
snapshotsConfig:
location: gs://${BUCKET_NAME}/antigravity-cli-multiplex-demo/

---

apiVersion: ate.dev/v1alpha1
kind: ActorTemplate
metadata:
name: agent-mars
namespace: antigravity-cli-multiplex-demo
spec:
runsc:
amd64:
url: "gs://gvisor/releases/nightly/2026-05-19/x86_64/runsc"
sha256Hash: "a397be1abc2420d26bce6c70e6e2ff96c73aaaab929756c56f5e2089ea842b63"
arm64:
url: "gs://gvisor/releases/nightly/2026-05-19/aarch64/runsc"
sha256Hash: "1ba2366ae2efceba166046f51a4104f9261c9cb72c6db8f5b3fe2dc57dea86b9"
pauseImage: "registry.k8s.io/pause:3.10.2@sha256:f548e0e8e3dc1896ca956272154dde3314e8cc4fde0a57577ee9fa1c63f5baf4"
containers:
- name: antigravity
image: ${WORKLOAD_IMAGE}
command: ["/run.sh"]
env:
- name: ACTOR_NAME
value: "mars"
- name: TASK
value: "Give me one concise tip for learning a new programming language. One sentence."
- name: INTERVAL_SECONDS
value: "45"
- name: ANTIGRAVITY_COMMAND
value: "antigravity --help"
workerPoolRef:
namespace: antigravity-cli-multiplex-demo
name: antigravity-workerpool
snapshotsConfig:
location: gs://${BUCKET_NAME}/antigravity-cli-multiplex-demo/

---

apiVersion: ate.dev/v1alpha1
kind: ActorTemplate
metadata:
name: agent-orion
namespace: antigravity-cli-multiplex-demo
spec:
runsc:
amd64:
url: "gs://gvisor/releases/nightly/2026-05-19/x86_64/runsc"
sha256Hash: "a397be1abc2420d26bce6c70e6e2ff96c73aaaab929756c56f5e2089ea842b63"
arm64:
url: "gs://gvisor/releases/nightly/2026-05-19/aarch64/runsc"
sha256Hash: "1ba2366ae2efceba166046f51a4104f9261c9cb72c6db8f5b3fe2dc57dea86b9"
pauseImage: "registry.k8s.io/pause:3.10.2@sha256:f548e0e8e3dc1896ca956272154dde3314e8cc4fde0a57577ee9fa1c63f5baf4"
containers:
- name: antigravity
image: ${WORKLOAD_IMAGE}
command: ["/run.sh"]
env:
- name: ACTOR_NAME
value: "orion"
- name: TASK
value: "Suggest one healthy meal that takes under 15 minutes to prepare. One sentence."
- name: INTERVAL_SECONDS
value: "45"
- name: ANTIGRAVITY_COMMAND
value: "antigravity --help"
workerPoolRef:
namespace: antigravity-cli-multiplex-demo
name: antigravity-workerpool
snapshotsConfig:
location: gs://${BUCKET_NAME}/antigravity-cli-multiplex-demo/
Loading