{
  "description": "AgentHarness is a generic remote execution environment provisioned by a backend\n(e.g. OpenShell) and addressable by exec/SSH.",
  "properties": {
    "apiVersion": {
      "description": "APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources",
      "type": [
        "string",
        "null"
      ]
    },
    "kind": {
      "description": "Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds",
      "type": [
        "string",
        "null"
      ]
    },
    "metadata": {
      "type": [
        "object",
        "null"
      ]
    },
    "spec": {
      "additionalProperties": false,
      "description": "AgentHarnessSpec describes a generic remote execution environment that agents\n(or human operators) can attach to via exec or SSH.\n\nAn AgentHarness is distinct from a SandboxAgent: it has no agent runtime baked\nin. The backend is responsible for provisioning an environment that stays\nready to accept incoming commands.",
      "properties": {
        "backend": {
          "description": "Backend selects the control plane to use. Required.",
          "enum": [
            "openshell",
            "openclaw",
            "nemoclaw"
          ],
          "type": "string"
        },
        "channels": {
          "description": "Channels configures Telegram and Slack integrations for OpenClaw inside the harness VM.\nOnly supported when backend is openclaw or nemoclaw.",
          "items": {
            "additionalProperties": false,
            "description": "AgentHarnessChannel declares one messenger binding inside an OpenClaw/NemoClaw harness VM.",
            "properties": {
              "name": {
                "description": "Name is a stable id for this binding (OpenClaw channels.*.accounts key).",
                "minLength": 1,
                "type": "string"
              },
              "slack": {
                "additionalProperties": false,
                "description": "AgentHarnessSlackChannelSpec configures Slack when AgentHarnessChannel.type is Slack.",
                "properties": {
                  "allowlistChannels": {
                    "items": {
                      "type": "string"
                    },
                    "type": [
                      "array",
                      "null"
                    ]
                  },
                  "appToken": {
                    "additionalProperties": false,
                    "description": "AgentHarnessChannelCredential supplies a token from an inline value or a Secret/ConfigMap key.",
                    "properties": {
                      "value": {
                        "maxLength": 8192,
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "valueFrom": {
                        "additionalProperties": false,
                        "description": "ValueSource defines a source for configuration values from a Secret or ConfigMap",
                        "properties": {
                          "key": {
                            "description": "The key of the ConfigMap or Secret.",
                            "maxLength": 253,
                            "type": "string"
                          },
                          "name": {
                            "description": "The name of the ConfigMap or Secret.",
                            "maxLength": 253,
                            "type": "string"
                          },
                          "type": {
                            "enum": [
                              "ConfigMap",
                              "Secret"
                            ],
                            "type": "string"
                          }
                        },
                        "required": [
                          "key",
                          "name",
                          "type"
                        ],
                        "type": [
                          "object",
                          "null"
                        ]
                      }
                    },
                    "type": "object",
                    "x-kubernetes-validations": [
                      {
                        "message": "Exactly one of value or valueFrom must be specified",
                        "rule": "(has(self.value) \u0026\u0026 !has(self.valueFrom)) || (!has(self.value) \u0026\u0026 has(self.valueFrom))"
                      }
                    ]
                  },
                  "botToken": {
                    "additionalProperties": false,
                    "description": "AgentHarnessChannelCredential supplies a token from an inline value or a Secret/ConfigMap key.",
                    "properties": {
                      "value": {
                        "maxLength": 8192,
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "valueFrom": {
                        "additionalProperties": false,
                        "description": "ValueSource defines a source for configuration values from a Secret or ConfigMap",
                        "properties": {
                          "key": {
                            "description": "The key of the ConfigMap or Secret.",
                            "maxLength": 253,
                            "type": "string"
                          },
                          "name": {
                            "description": "The name of the ConfigMap or Secret.",
                            "maxLength": 253,
                            "type": "string"
                          },
                          "type": {
                            "enum": [
                              "ConfigMap",
                              "Secret"
                            ],
                            "type": "string"
                          }
                        },
                        "required": [
                          "key",
                          "name",
                          "type"
                        ],
                        "type": [
                          "object",
                          "null"
                        ]
                      }
                    },
                    "type": "object",
                    "x-kubernetes-validations": [
                      {
                        "message": "Exactly one of value or valueFrom must be specified",
                        "rule": "(has(self.value) \u0026\u0026 !has(self.valueFrom)) || (!has(self.value) \u0026\u0026 has(self.valueFrom))"
                      }
                    ]
                  },
                  "channelAccess": {
                    "description": "AgentHarnessChannelAccess controls whether the bot listens broadly or only on an allowlist.",
                    "enum": [
                      "allowlist",
                      "open",
                      "disabled"
                    ],
                    "type": "string"
                  },
                  "interactiveReplies": {
                    "default": true,
                    "type": [
                      "boolean",
                      "null"
                    ]
                  }
                },
                "required": [
                  "appToken",
                  "botToken",
                  "channelAccess"
                ],
                "type": [
                  "object",
                  "null"
                ],
                "x-kubernetes-validations": [
                  {
                    "message": "allowlistChannels is required when channelAccess is allowlist",
                    "rule": "self.channelAccess != 'allowlist' || (has(self.allowlistChannels) \u0026\u0026 size(self.allowlistChannels) \u003e 0)"
                  }
                ]
              },
              "telegram": {
                "additionalProperties": false,
                "description": "AgentHarnessTelegramChannelSpec configures Telegram when AgentHarnessChannel.type is Telegram.",
                "properties": {
                  "allowedUserIDs": {
                    "items": {
                      "type": "string"
                    },
                    "type": [
                      "array",
                      "null"
                    ]
                  },
                  "allowedUserIDsFrom": {
                    "additionalProperties": false,
                    "description": "ValueSource defines a source for configuration values from a Secret or ConfigMap",
                    "properties": {
                      "key": {
                        "description": "The key of the ConfigMap or Secret.",
                        "maxLength": 253,
                        "type": "string"
                      },
                      "name": {
                        "description": "The name of the ConfigMap or Secret.",
                        "maxLength": 253,
                        "type": "string"
                      },
                      "type": {
                        "enum": [
                          "ConfigMap",
                          "Secret"
                        ],
                        "type": "string"
                      }
                    },
                    "required": [
                      "key",
                      "name",
                      "type"
                    ],
                    "type": [
                      "object",
                      "null"
                    ]
                  },
                  "botToken": {
                    "additionalProperties": false,
                    "description": "AgentHarnessChannelCredential supplies a token from an inline value or a Secret/ConfigMap key.",
                    "properties": {
                      "value": {
                        "maxLength": 8192,
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "valueFrom": {
                        "additionalProperties": false,
                        "description": "ValueSource defines a source for configuration values from a Secret or ConfigMap",
                        "properties": {
                          "key": {
                            "description": "The key of the ConfigMap or Secret.",
                            "maxLength": 253,
                            "type": "string"
                          },
                          "name": {
                            "description": "The name of the ConfigMap or Secret.",
                            "maxLength": 253,
                            "type": "string"
                          },
                          "type": {
                            "enum": [
                              "ConfigMap",
                              "Secret"
                            ],
                            "type": "string"
                          }
                        },
                        "required": [
                          "key",
                          "name",
                          "type"
                        ],
                        "type": [
                          "object",
                          "null"
                        ]
                      }
                    },
                    "type": "object",
                    "x-kubernetes-validations": [
                      {
                        "message": "Exactly one of value or valueFrom must be specified",
                        "rule": "(has(self.value) \u0026\u0026 !has(self.valueFrom)) || (!has(self.value) \u0026\u0026 has(self.valueFrom))"
                      }
                    ]
                  }
                },
                "required": [
                  "botToken"
                ],
                "type": [
                  "object",
                  "null"
                ],
                "x-kubernetes-validations": [
                  {
                    "message": "allowedUserIDs and allowedUserIDsFrom are mutually exclusive",
                    "rule": "!(size(self.allowedUserIDs) \u003e 0 \u0026\u0026 has(self.allowedUserIDsFrom))"
                  }
                ]
              },
              "type": {
                "description": "AgentHarnessChannelType selects a messenger integration for OpenClaw harness VMs.",
                "enum": [
                  "telegram",
                  "slack"
                ],
                "type": "string"
              }
            },
            "required": [
              "name",
              "type"
            ],
            "type": "object",
            "x-kubernetes-validations": [
              {
                "message": "exactly one of telegram or slack must be set and must match type",
                "rule": "(self.type == 'telegram' \u0026\u0026 has(self.telegram) \u0026\u0026 !has(self.slack)) || (self.type == 'slack' \u0026\u0026 has(self.slack) \u0026\u0026 !has(self.telegram))"
              }
            ]
          },
          "type": [
            "array",
            "null"
          ]
        },
        "description": {
          "description": "Description is a short human-readable summary shown in the UI (e.g. agents list).",
          "type": [
            "string",
            "null"
          ]
        },
        "env": {
          "description": "Env is a list of environment variables injected into the harness workload.\nValues use the Kubernetes EnvVar shape; ValueFrom references are\nresolved server-side where supported.",
          "items": {
            "additionalProperties": false,
            "description": "EnvVar represents an environment variable present in a Container.",
            "properties": {
              "name": {
                "description": "Name of the environment variable.\nMay consist of any printable ASCII characters except '='.",
                "type": "string"
              },
              "value": {
                "description": "Variable references $(VAR_NAME) are expanded\nusing the previously defined environment variables in the container and\nany service environment variables. If a variable cannot be resolved,\nthe reference in the input string will be unchanged. Double $$ are reduced\nto a single $, which allows for escaping the $(VAR_NAME) syntax: i.e.\n\"$$(VAR_NAME)\" will produce the string literal \"$(VAR_NAME)\".\nEscaped references will never be expanded, regardless of whether the variable\nexists or not.\nDefaults to \"\".",
                "type": [
                  "string",
                  "null"
                ]
              },
              "valueFrom": {
                "additionalProperties": false,
                "description": "Source for the environment variable's value. Cannot be used if value is not empty.",
                "properties": {
                  "configMapKeyRef": {
                    "additionalProperties": false,
                    "description": "Selects a key of a ConfigMap.",
                    "properties": {
                      "key": {
                        "description": "The key to select.",
                        "type": "string"
                      },
                      "name": {
                        "default": "",
                        "description": "Name of the referent.\nThis field is effectively required, but due to backwards compatibility is\nallowed to be empty. Instances of this type with an empty value here are\nalmost certainly wrong.\nMore info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names",
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "optional": {
                        "description": "Specify whether the ConfigMap or its key must be defined",
                        "type": [
                          "boolean",
                          "null"
                        ]
                      }
                    },
                    "required": [
                      "key"
                    ],
                    "type": [
                      "object",
                      "null"
                    ],
                    "x-kubernetes-map-type": "atomic"
                  },
                  "fieldRef": {
                    "additionalProperties": false,
                    "description": "Selects a field of the pod: supports metadata.name, metadata.namespace, `metadata.labels['\u003cKEY\u003e']`, `metadata.annotations['\u003cKEY\u003e']`,\nspec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP, status.podIPs.",
                    "properties": {
                      "apiVersion": {
                        "description": "Version of the schema the FieldPath is written in terms of, defaults to \"v1\".",
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "fieldPath": {
                        "description": "Path of the field to select in the specified API version.",
                        "type": "string"
                      }
                    },
                    "required": [
                      "fieldPath"
                    ],
                    "type": [
                      "object",
                      "null"
                    ],
                    "x-kubernetes-map-type": "atomic"
                  },
                  "fileKeyRef": {
                    "additionalProperties": false,
                    "description": "FileKeyRef selects a key of the env file.\nRequires the EnvFiles feature gate to be enabled.",
                    "properties": {
                      "key": {
                        "description": "The key within the env file. An invalid key will prevent the pod from starting.\nThe keys defined within a source may consist of any printable ASCII characters except '='.\nDuring Alpha stage of the EnvFiles feature gate, the key size is limited to 128 characters.",
                        "type": "string"
                      },
                      "optional": {
                        "default": false,
                        "description": "Specify whether the file or its key must be defined. If the file or key\ndoes not exist, then the env var is not published.\nIf optional is set to true and the specified key does not exist,\nthe environment variable will not be set in the Pod's containers.\n\nIf optional is set to false and the specified key does not exist,\nan error will be returned during Pod creation.",
                        "type": [
                          "boolean",
                          "null"
                        ]
                      },
                      "path": {
                        "description": "The path within the volume from which to select the file.\nMust be relative and may not contain the '..' path or start with '..'.",
                        "type": "string"
                      },
                      "volumeName": {
                        "description": "The name of the volume mount containing the env file.",
                        "type": "string"
                      }
                    },
                    "required": [
                      "key",
                      "path",
                      "volumeName"
                    ],
                    "type": [
                      "object",
                      "null"
                    ],
                    "x-kubernetes-map-type": "atomic"
                  },
                  "resourceFieldRef": {
                    "additionalProperties": false,
                    "description": "Selects a resource of the container: only resources limits and requests\n(limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported.",
                    "properties": {
                      "containerName": {
                        "description": "Container name: required for volumes, optional for env vars",
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "divisor": {
                        "description": "Specifies the output format of the exposed resources, defaults to \"1\"",
                        "oneOf": [
                          {
                            "pattern": "^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$",
                            "type": "string"
                          },
                          {
                            "type": "integer"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "x-kubernetes-int-or-string": true
                      },
                      "resource": {
                        "description": "Required: resource to select",
                        "type": "string"
                      }
                    },
                    "required": [
                      "resource"
                    ],
                    "type": [
                      "object",
                      "null"
                    ],
                    "x-kubernetes-map-type": "atomic"
                  },
                  "secretKeyRef": {
                    "additionalProperties": false,
                    "description": "Selects a key of a secret in the pod's namespace",
                    "properties": {
                      "key": {
                        "description": "The key of the secret to select from.  Must be a valid secret key.",
                        "type": "string"
                      },
                      "name": {
                        "default": "",
                        "description": "Name of the referent.\nThis field is effectively required, but due to backwards compatibility is\nallowed to be empty. Instances of this type with an empty value here are\nalmost certainly wrong.\nMore info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names",
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "optional": {
                        "description": "Specify whether the Secret or its key must be defined",
                        "type": [
                          "boolean",
                          "null"
                        ]
                      }
                    },
                    "required": [
                      "key"
                    ],
                    "type": [
                      "object",
                      "null"
                    ],
                    "x-kubernetes-map-type": "atomic"
                  }
                },
                "type": [
                  "object",
                  "null"
                ]
              }
            },
            "required": [
              "name"
            ],
            "type": "object"
          },
          "type": [
            "array",
            "null"
          ]
        },
        "image": {
          "description": "Image is the container image to run in the harness VM, if the backend\nsupports per-resource images. Backends openclaw and nemoclaw pin the image\nto the NemoClaw sandbox base; openshell uses spec.image when set.",
          "type": [
            "string",
            "null"
          ]
        },
        "modelConfigRef": {
          "description": "ModelConfigRef is the reference to the ModelConfig used to configure the harness.\nWhen set with backend openclaw or nemoclaw, the controller registers the gateway provider and,\nafter the harness is Ready, writes OpenClaw config inside the VM (~/.openclaw/openclaw.json) and starts the gateway.\nIt is ignored for backend openshell.",
          "type": [
            "string",
            "null"
          ]
        },
        "network": {
          "additionalProperties": false,
          "description": "Network controls outbound access from the harness. When unset,\nbackend defaults apply.",
          "properties": {
            "allowedDomains": {
              "description": "AllowedDomains is a list of DNS names the harness may reach.",
              "items": {
                "type": "string"
              },
              "type": [
                "array",
                "null"
              ]
            }
          },
          "type": [
            "object",
            "null"
          ]
        }
      },
      "required": [
        "backend"
      ],
      "type": [
        "object",
        "null"
      ],
      "x-kubernetes-validations": [
        {
          "message": "channels may only be set when backend is openclaw or nemoclaw",
          "rule": "!has(self.channels) || size(self.channels) == 0 || self.backend == 'openclaw' || self.backend == 'nemoclaw'"
        }
      ]
    },
    "status": {
      "additionalProperties": false,
      "description": "AgentHarnessStatus is the observed state of an AgentHarness.",
      "properties": {
        "backendRef": {
          "additionalProperties": false,
          "description": "BackendRef points at the harness instance on the backend control\nplane, once Ensure has succeeded at least once.",
          "properties": {
            "backend": {
              "description": "AgentHarnessBackendType selects which sandbox control plane provisions the\nenvironment. Additional backends may be added in the future.",
              "enum": [
                "openshell",
                "openclaw",
                "nemoclaw"
              ],
              "type": "string"
            },
            "id": {
              "type": "string"
            }
          },
          "required": [
            "backend",
            "id"
          ],
          "type": [
            "object",
            "null"
          ]
        },
        "conditions": {
          "items": {
            "additionalProperties": false,
            "description": "Condition contains details for one aspect of the current state of this API Resource.",
            "properties": {
              "lastTransitionTime": {
                "description": "lastTransitionTime is the last time the condition transitioned from one status to another.\nThis should be when the underlying condition changed.  If that is not known, then using the time when the API field changed is acceptable.",
                "format": "date-time",
                "type": "string"
              },
              "message": {
                "description": "message is a human readable message indicating details about the transition.\nThis may be an empty string.",
                "maxLength": 32768,
                "type": "string"
              },
              "observedGeneration": {
                "description": "observedGeneration represents the .metadata.generation that the condition was set based upon.\nFor instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\nwith respect to the current state of the instance.",
                "format": "int64",
                "minimum": 0,
                "type": [
                  "integer",
                  "null"
                ]
              },
              "reason": {
                "description": "reason contains a programmatic identifier indicating the reason for the condition's last transition.\nProducers of specific condition types may define expected values and meanings for this field,\nand whether the values are considered a guaranteed API.\nThe value should be a CamelCase string.\nThis field may not be empty.",
                "maxLength": 1024,
                "minLength": 1,
                "pattern": "^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$",
                "type": "string"
              },
              "status": {
                "description": "status of the condition, one of True, False, Unknown.",
                "enum": [
                  "True",
                  "False",
                  "Unknown"
                ],
                "type": "string"
              },
              "type": {
                "description": "type of condition in CamelCase or in foo.example.com/CamelCase.",
                "maxLength": 316,
                "pattern": "^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$",
                "type": "string"
              }
            },
            "required": [
              "lastTransitionTime",
              "message",
              "reason",
              "status",
              "type"
            ],
            "type": "object"
          },
          "type": [
            "array",
            "null"
          ]
        },
        "connection": {
          "additionalProperties": false,
          "description": "Connection is populated by the controller when the harness is ready.",
          "properties": {
            "endpoint": {
              "description": "Endpoint is the backend-specific address (gRPC target, SSH host:port,\n...) clients should use to reach the harness.",
              "type": [
                "string",
                "null"
              ]
            }
          },
          "type": [
            "object",
            "null"
          ]
        },
        "observedGeneration": {
          "format": "int64",
          "type": [
            "integer",
            "null"
          ]
        }
      },
      "type": [
        "object",
        "null"
      ]
    }
  },
  "type": "object"
}