Kubernetes controller for the agentic proposal workflow (agentic.openshift.io/v1alpha1): AgenticRun, AgenticRunApproval, Agent, LLMProvider, ApprovalPolicy, and related resources.
- How to work (agents):
agent.md - Two Go modules (
go.mod/go.sumat root and underapi/): below; directory map, phases, conventions:CLAUDE.md - Tests, manifests, Makefile, cluster workflow,
make api-lint, CEL /XValidation: this file
The oc-agentic binary is an oc CLI plugin for managing proposals. Place it in $PATH and oc auto-discovers it as oc agentic <subcommand>. It also works standalone.
# Linux amd64
curl -L https://github.com/openshift/lightspeed-agentic-operator/releases/latest/download/oc-agentic_linux_amd64.tar.gz | tar xz
sudo mv oc-agentic /usr/local/bin/
# macOS Apple Silicon
curl -L https://github.com/openshift/lightspeed-agentic-operator/releases/latest/download/oc-agentic_darwin_arm64.tar.gz | tar xz
sudo mv oc-agentic /usr/local/bin/# Create a proposal
oc agentic run create --request="Fix crashloop in my-app namespace" --target-namespaces=my-app
# List proposals
oc agentic run list
# Inspect a proposal
oc agentic run get ag-abc12
# Approve steps (analysis → execution → verification)
oc agentic run approve ag-abc12 --stage=analysis
oc agentic run approve ag-abc12 --stage=execution --option=0
oc agentic run approve ag-abc12 --stage=verification
# Or approve all pending steps at once
oc agentic run approve ag-abc12 --all --wait
# Deny a step
oc agentic run deny ag-abc12 --stage=execution
# Watch phase transitions
oc agentic run watch ag-abc12
# Stream sandbox logs
oc agentic run logs ag-abc12 --step=Execution -f
# Delete a proposal
oc agentic run delete ag-abc12
# System operations
oc agentic status # check if system is active or suspended
oc agentic suspend --yes # halt all agentic operations
oc agentic resume # resume operations
oc agentic version| Command | Description |
|---|---|
proposal create |
Create a new proposal (--request, --agent, --target-namespaces) |
proposal list (ls) |
List proposals (-A, --phase, -o wide|json|yaml) |
proposal get |
Show proposal details (-o json|yaml) |
proposal approve |
Approve a step (--stage, --option, --agent, --all, --wait) |
proposal deny |
Deny a step (--stage, defaults to next pending) |
proposal watch |
Stream phase transitions until terminal |
proposal logs |
Stream sandbox pod logs (--step, -f) |
proposal delete |
Delete a proposal |
status |
Show system suspension state |
suspend |
Suspend all operations (--yes to skip prompt) |
resume |
Resume operations |
version |
Print plugin version |
Default namespace is openshift-lightspeed unless overridden with -n or kubeconfig context.
Run the manager against a cluster using the usual controller-runtime kubeconfig rules (KUBECONFIG, default kubeconfig path, or in-cluster as a pod). Auth plugins (OIDC, GCP, Azure, …) are registered via k8s.io/client-go/plugin/pkg/client/auth.
This repo has two Go modules:
| Location | Module path | Role |
|---|---|---|
Repo root go.mod |
github.com/openshift/lightspeed-agentic-operator |
Controller, CLI, cmd/, controller/, etc. |
api/go.mod |
github.com/openshift/lightspeed-agentic-operator/api |
CRD types and API helpers only—downstreams can require this module without pulling the full operator graph. |
The root module replaces …/api => ./api for local builds. The Dockerfile copies both pairs of go.mod / go.sum before go mod download.
When changing Kubernetes / controller-runtime versions, keep the two go.mod files in sync (same PR / same intent) and run go mod tidy at the root and under api/ as needed.
make test and make api-lint run tests and the kube API linter under api/ with GOWORK=off so a repo-root go.work does not force the wrong module boundary.
Reconciling proposals uses kubernetes-sigs/agent-sandbox (SandboxClaim, Sandbox, SandboxTemplate). make run and make deploy both run install-agent-sandbox first: it only hits the network if the three Sandbox CRDs are missing (otherwise three cheap kubectl get crd calls). You can run make install-agent-sandbox alone to set AGENT_SANDBOX_VERSION without a full make run.
make install-agent-sandbox # optional; also run automatically before make run / make deploy
# Override release: make install-agent-sandbox AGENT_SANDBOX_VERSION=v0.4.5Upstream install details: Agent Sandbox installation. The Makefile target applies manifest.yaml and extensions.yaml for AGENT_SANDBOX_VERSION (default pinned in the Makefile). You still need a SandboxTemplate named like TEMPLATE_NAME (default lightspeed-agent) in OPERATOR_NAMESPACE — usually from Lightspeed / your platform, not from this target.
Do not edit config/crd/bases/ or generated config/rbac/role.yaml by hand. After changing api/v1alpha1/ types or kubebuilder markers, run make manifests.
OPERATOR_NAMESPACE defaults to default: used for make run (--namespace), and for make deploy / make undeploy (Namespace, Deployment, ServiceAccount, RBAC subject namespace). Manifests under config/ use __OPERATOR_NAMESPACE__; the Makefile substitutes $(OPERATOR_NAMESPACE) before kustomize build. Local make run uses metrics :18080 and health :18081 by default; override with METRICS_BIND_ADDRESS and HEALTH_PROBE_BIND_ADDRESS if needed.
make manifests # controller-gen → config/crd/bases + config/rbac/role.yaml
make test # fmt-check + vet + go test (root + api module)
make install # kubectl apply CRDs from config/crd only
make run # install + install-agent-sandbox + vet + go run ./cmd/main.go
make uninstall # kubectl delete CRDs from config/crd (optional: ignore-not-found=true)
make deploy # apply only; IMG must be pullable (CI / released image)
make deploy-local # OpenShift (see Makefile)
make undeploy # same layout | kubectl delete (optional: ignore-not-found=true)
make api-lint # Kube API linter on api/ (golangci-lint custom + plugin; see below)make test runs fmt-check (fails if any file needs reformatting — run make fmt to fix), vet, root-module tests, and cd api && GOWORK=off go test ./... -count=1 (the API tree is a separate module; GOWORK=off avoids a repo-root go.work hijacking module choice). The test/e2e package is excluded from this target — it requires -tags=e2e and a running mock agent.
make test-e2e runs go test -tags=e2e ./test/e2e/... against a live cluster with the operator running. Prerequisites: make run TEMPLATE_NAME=lightspeed-agent-mock (or deployed operator with --template-name=lightspeed-agent-mock) and mock agent SandboxTemplate applied (kubectl apply -k test/agent/sandboxtemplate). See test/e2e/ package doc for details.
Product E2E runs the troubleshooting scenarios against a real LLM provider. It deploys (or reuses) the operator, clones rhobs/troubleshooting-scenarios, and runs the product_e2e-tagged tests. For example, to run Claude scenarios:
IMG=quay.io/.../lightspeed-agentic-operator@sha256:<digest> \
VERTEX_PROVIDER_KEY_PATH=/path/to/service-account.json \
VERTEX_PROJECT_ID=my-gcp-project \
ARTIFACT_DIR="$PWD/artifacts" \
bash scripts/e2e-cluster.sh claudeclaude and gemini require VERTEX_PROVIDER_KEY_PATH and VERTEX_PROJECT_ID; openai requires OPENAI_PROVIDER_KEY_PATH. The runner accepts one or more providers (claude, gemini, and/or openai).
| Variable | Purpose |
|---|---|
E2E_SCENARIO_TAGS=alert |
Run scenarios carrying the alert tag instead of the default core tag. Multiple tags are an AND filter: core,alert requires both. |
E2E_SKIP_SCENARIOS=pending_pvc_alert |
Skip comma-separated scenario directory names. |
E2E_SCENARIO_TIMEOUT=20m |
Per-scenario deadline (default: 20m). |
E2E_OTEL_ENABLED=false |
Disable the persistent OTEL/Postgres collector used for product-E2E artifacts. It is enabled by default. |
E2E_OTEL_IMAGE=quay.io/.../collector:tag |
Override the OTEL collector image. |
ARTIFACT_DIR=/path/to/artifacts |
Directory where test and diagnostic artifacts are written. |
With ARTIFACT_DIR set, artifacts are arranged per provider:
artifacts/<provider>/
├── product-e2e-output.log # Go test output
├── operator-logs.txt # controller-manager logs
├── pods.yaml # remaining operator-namespace pods
├── podlogs/ # best-effort direct sandbox-pod log backup
└── runs/<run-name>/
└── otel-sandbox.json # persisted OTEL collector records for the run
The runner configures the collector with its Postgres backend, because the default nop pipeline drops records and does not expose the admin API used to export per-run records. Direct pod-log capture is best effort: sandbox pods may be removed immediately after completion, so persisted OTEL records are the primary diagnostic source.
For noisy debugging: go test ./controller/agenticrun/... -v, go test ./api/... -v, go test ./cli/... -v.
make api-lint installs golangci-lint to bin/ (version from .custom-gcl.yml), runs golangci-lint custom (builds bin/golangci-lint-kube-api-linter), then lints api/ with GOWORK=off and .golangci-kal.yml.
kubebuilder +kubebuilder:validation:XValidation markers become CEL rules in generated CRD YAML (make manifests).
omitemptyfields: they are absent from the stored object when unset. CEL must usehas(field)before reading them, or validation fails with no such key. Example:has(old.denied) && old.deniedinstead ofold.deniedalone.- Marker placement: struct-level
XValidationcan produce double-quoted strings in generated YAML. PutXValidationon the field in the parent struct, not on the nested type.
kustomize build | kubectl apply. IMG is the image reference the Deployment uses; the image must already exist on the cluster.
make deploy-local — build, push to the cluster registry, and deploy the operator (oc and a push-capable registry required). Details live in the deploy-local recipe in the Makefile.
$(CONTAINER_TOOL) build -t $(IMG) . — see Dockerfile.