Deploy AI agents in isolated Kubernetes sandboxes.
KAgent orchestrates AI agents; OpenShell provides network-isolated sandbox pods managed by the Agent Sandbox Controller; NemoClaw is the AgentHarness that wires them together via the OpenClaw backend. Optionally connects to a Telegram bot channel.
┌──────────────────────────────────────────────┐
│ kagent namespace │
│ ┌───────────────┐ ┌────────────────────┐ │
│ │ kagent-ctrl │───▶│ AgentHarness │ │
│ └───────────────┘ │ (nemoclaw) │ │
│ └────────┬───────────┘ │
└────────────────────────────────┼─────────────┘
│ OpenClaw backend
┌────────────────────────────────▼─────────────┐
│ openshell namespace │
│ ┌────────────────┐ ┌────────────────────┐ │
│ │ openshell gw │──▶│ sandbox pod │ │
│ │ (statefulset) │ │ (network-isolated) │ │
│ └────────────────┘ └────────────────────┘ │
└──────────────────────────────────────────────┘
Policy enforcement: Kyverno (privileged + capabilities)
Sandbox CRD: agent-sandbox-controller
kubectlconfigured against the target clusterhelmv3+- Kubernetes 1.25+ (tested on K3s 1.35 / Ubuntu 24.04)
- OpenAI API key
| Variable | Required | Default | Description |
|---|---|---|---|
OPENAI_API_KEY |
✓ | — | OpenAI key injected into KAgent |
TELEGRAM_BOT_TOKEN |
— | — | Bot token; enables Telegram channel |
TELEGRAM_ALLOWED_USERS |
— | — | Comma-separated Telegram user IDs |
KYVERNO_VERSION |
— | 3.4.4 |
|
KAGENT_VERSION |
— | 0.9.2 |
|
OPENSHELL_CHART_VERSION |
— | 0.1.0 |
|
AGENT_SANDBOX_VERSION |
— | v0.4.5 |
|
SANDBOX_IMAGE |
— | ghcr.io/kagent-dev/nemoclaw/sandbox-base:2026.5.4 |
|
HARNESS_NAME |
— | nemoclaw |
Name of the AgentHarness CR |
export OPENAI_API_KEY=sk-...
# export TELEGRAM_BOT_TOKEN=1234:AAE...
# export TELEGRAM_ALLOWED_USERS=8672152974
chmod +x install.sh && ./install.shThe sections below reproduce every command from install.sh as copy-paste blocks. Use these if you prefer full control or want to run only specific steps.
Set these shell variables before running any step:
export KYVERNO_VERSION=3.4.4
export KAGENT_VERSION=0.9.2
export OPENSHELL_CHART_VERSION=0.1.0
export AGENT_SANDBOX_VERSION=v0.4.5
export SANDBOX_IMAGE=ghcr.io/kagent-dev/nemoclaw/sandbox-base:2026.5.4
export HARNESS_NAME=nemoclaw
export OPENSHELL_NS=openshell
export KAGENT_NS=kagent
export SANDBOX_SYS_NS=agent-sandbox-systemInstall Kyverno:
helm upgrade --install kyverno \
oci://ghcr.io/kyverno/charts/kyverno \
--version "$KYVERNO_VERSION" \
--namespace kyverno \
--create-namespace \
--wait --timeout=300sGrant Kyverno read access to Sandbox CRs:
kubectl apply -f - <<'EOF'
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: kyverno-sandbox-reporter
rules:
- apiGroups: ["agents.x-k8s.io"]
resources: ["sandboxes"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: kyverno-sandbox-reporter
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: kyverno-sandbox-reporter
subjects:
- kind: ServiceAccount
name: kyverno-reports-controller
namespace: kyverno
EOFApply the runtime policy that grants sandbox pods privileged access:
kubectl apply -f - <<'EOF'
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: openshell-runtime-access
spec:
rules:
- name: privileged-openshell
match:
any:
- resources:
kinds:
- Sandbox
namespaces:
- openshell
selector:
matchLabels:
openshell.ai/managed-by: openshell
mutate:
foreach:
- list: "request.object.spec.podTemplate.spec.containers"
patchesJson6902: |-
- op: add
path: /spec/podTemplate/spec/containers/{{elementIndex}}/securityContext/privileged
value: true
- op: add
path: /spec/podTemplate/spec/containers/{{elementIndex}}/securityContext/runAsUser
value: 0
- op: add
path: /spec/podTemplate/spec/containers/{{elementIndex}}/securityContext/capabilities
value:
add:
- SYS_ADMIN
- NET_ADMIN
- SYS_PTRACE
- SYSLOG
EOFkubectl apply -f "https://github.com/kubernetes-sigs/agent-sandbox/releases/download/${AGENT_SANDBOX_VERSION}/manifest.yaml"
kubectl rollout status deployment/agent-sandbox-controller \
-n "$SANDBOX_SYS_NS" --timeout=120sApply the RBAC fix for event recording:
kubectl apply -f - <<EOF
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: agent-sandbox-controller-events
namespace: $SANDBOX_SYS_NS
rules:
- apiGroups: [""]
resources: ["events"]
verbs: ["create","patch","update"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: agent-sandbox-controller-events
namespace: $SANDBOX_SYS_NS
subjects:
- kind: ServiceAccount
name: agent-sandbox-controller
namespace: $SANDBOX_SYS_NS
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: agent-sandbox-controller-events
EOFCreate namespace and SSH handshake secret:
kubectl create namespace "$OPENSHELL_NS" --dry-run=client -o yaml | kubectl apply -f -
kubectl create secret generic openshell-ssh-handshake \
--from-literal=secret="$(openssl rand -hex 32)" \
-n "$OPENSHELL_NS"Install the OpenShell chart:
helm install openshell \
oci://ghcr.io/kagent-dev/openshell/charts/openshell \
--version "$OPENSHELL_CHART_VERSION" \
--namespace "$OPENSHELL_NS" \
--values - <<EOF
image:
repository: ghcr.io/kagent-dev/openshell/gateway
tag: latest
pullPolicy: Always
supervisor:
image:
repository: ghcr.io/kagent-dev/openshell/supervisor
tag: latest
server:
sandboxNamespace: $OPENSHELL_NS
sandboxImage: $SANDBOX_IMAGE
disableTls: true
disableGatewayAuth: false
sshHandshake:
hook:
enabled: false
EOF
kubectl rollout status statefulset/openshell -n "$OPENSHELL_NS" --timeout=120sCreate the namespace and inject the OpenAI secret:
kubectl create namespace "$KAGENT_NS" --dry-run=client -o yaml | kubectl apply -f -
kubectl create secret generic kagent-openai \
--from-literal=OPENAI_API_KEY="$OPENAI_API_KEY" \
-n "$KAGENT_NS" --dry-run=client -o yaml | kubectl apply -f -
# Allow Helm to adopt the pre-existing secret
kubectl annotate secret kagent-openai -n "$KAGENT_NS" \
meta.helm.sh/release-name=kagent \
meta.helm.sh/release-namespace="$KAGENT_NS" \
--overwrite
kubectl label secret kagent-openai -n "$KAGENT_NS" \
app.kubernetes.io/managed-by=Helm \
--overwriteInstall KAgent CRDs and controller:
helm upgrade --install kagent-crds \
oci://ghcr.io/kagent-dev/kagent/helm/kagent-crds \
--version "$KAGENT_VERSION" \
--namespace "$KAGENT_NS"
helm upgrade --install kagent \
oci://ghcr.io/kagent-dev/kagent/helm/kagent \
--set controller.image.registry=fjvicens \
--set controller.image.repository=kagent-ctrl \
--set controller.image.tag=latest \
--version "$KAGENT_VERSION" \
--namespace "$KAGENT_NS" \
--values - <<EOF
providers:
default: openAI
openAI:
apiKey: "$OPENAI_API_KEY"
controller:
env:
- name: OPENSHELL_GATEWAY_URL
value: "openshell.${OPENSHELL_NS}.svc.cluster.local:8080"
- name: OPENSHELL_INSECURE
value: "true"
EOF
kubectl rollout status deployment/kagent-controller -n "$KAGENT_NS" --timeout=120sApply the NemoClaw AgentHarness:
kubectl apply -f - <<EOF
apiVersion: kagent.dev/v1alpha2
kind: AgentHarness
metadata:
name: $HARNESS_NAME
namespace: $KAGENT_NS
spec:
backend: openclaw
modelConfigRef: default-model-config
EOFTelegram: To enable a Telegram channel, set
TELEGRAM_BOT_TOKENandTELEGRAM_ALLOWED_USERSand run./install.shinstead — it builds the channels block automatically. See TROUBLESHOOTING.md if the Telegram proxy policy gets stuck inpending.
Wait for the sandbox to be provisioned:
# Poll until backendRef is populated (up to 90s)
deadline=$((SECONDS + 90))
until kubectl get agentharness "$HARNESS_NAME" -n "$KAGENT_NS" \
-o jsonpath='{.status.backendRef.id}' 2>/dev/null | grep -q .; do
(( SECONDS < deadline )) || { echo "Timeout waiting for sandbox"; exit 1; }
sleep 3
done
echo "Sandbox: $(kubectl get agentharness "$HARNESS_NAME" -n "$KAGENT_NS" \
-o jsonpath='{.status.backendRef.id}')"Expose the KAgent UI:
kubectl patch svc kagent-ui -n kagent -p '{"spec":{"type":"LoadBalancer"}}'kubectl get pods -n openshell
kubectl get pods -n kagent
kubectl get agentharness -n kagent
kubectl logs -n kagent deployment/kagent-controller --tail=30- The Kyverno
openshell-runtime-accesspolicy automatically patches new sandbox pods withprivileged: true. If a sandbox pod was created before Kyverno was ready, delete it so the policy applies on re-creation. - Telegram channel requires
TELEGRAM_BOT_TOKENandTELEGRAM_ALLOWED_USERSto be set before the AgentHarness is created. The easiest path is to export them and run./install.sh. - See TROUBLESHOOTING.md for known OpenClaw provisioning issues and their workarounds.
