{"id":51230799,"url":"https://github.com/paperclipinc/hermes-operator","last_synced_at":"2026-06-28T16:01:21.799Z","repository":{"id":357545489,"uuid":"1236477821","full_name":"paperclipinc/hermes-operator","owner":"paperclipinc","description":"Production-grade Kubernetes operator for nousresearch/hermes-agent: declarative spec, security defaults, S3 backups, OCI auto-update with rollback, SSA-based GitOps coexistence, OpenClaw migration.","archived":false,"fork":false,"pushed_at":"2026-06-25T14:16:44.000Z","size":1351,"stargazers_count":19,"open_issues_count":0,"forks_count":6,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-06-25T14:22:47.747Z","etag":null,"topics":["ai-agent","cncf","devops","gitops","golang","helm","hermes","hermes-agent","kubernetes","kubernetes-operator","llm","olm","operator","operator-sdk","ssa"],"latest_commit_sha":null,"homepage":"https://paperclip.inc/hermes-agent","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/paperclipinc.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":"CODEOWNERS","security":"SECURITY.md","support":"SUPPORT.md","governance":"GOVERNANCE.md","roadmap":"ROADMAP.md","authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":"MAINTAINERS.md","copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-12T09:30:22.000Z","updated_at":"2026-06-25T14:17:32.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/paperclipinc/hermes-operator","commit_stats":null,"previous_names":["stubbi/hermes-operator","paperclipinc/hermes-operator"],"tags_count":23,"template":false,"template_full_name":null,"purl":"pkg:github/paperclipinc/hermes-operator","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/paperclipinc%2Fhermes-operator","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/paperclipinc%2Fhermes-operator/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/paperclipinc%2Fhermes-operator/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/paperclipinc%2Fhermes-operator/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/paperclipinc","download_url":"https://codeload.github.com/paperclipinc/hermes-operator/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/paperclipinc%2Fhermes-operator/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34894560,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-28T02:00:05.809Z","response_time":54,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai-agent","cncf","devops","gitops","golang","helm","hermes","hermes-agent","kubernetes","kubernetes-operator","llm","olm","operator","operator-sdk","ssa"],"created_at":"2026-06-28T16:01:18.976Z","updated_at":"2026-06-28T16:01:21.754Z","avatar_url":"https://github.com/paperclipinc.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Hermes Operator\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"LICENSE\"\u003e\u003cimg src=\"https://img.shields.io/badge/License-Apache_2.0-blue.svg\" alt=\"License\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://goreportcard.com/report/github.com/paperclipinc/hermes-operator\"\u003e\u003cimg src=\"https://goreportcard.com/badge/github.com/paperclipinc/hermes-operator\" alt=\"Go Report Card\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://github.com/paperclipinc/hermes-operator/actions/workflows/ci.yaml\"\u003e\u003cimg src=\"https://github.com/paperclipinc/hermes-operator/actions/workflows/ci.yaml/badge.svg\" alt=\"CI\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://github.com/paperclipinc/hermes-operator/actions/workflows/e2e.yaml\"\u003e\u003cimg src=\"https://github.com/paperclipinc/hermes-operator/actions/workflows/e2e.yaml/badge.svg\" alt=\"E2E\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://github.com/paperclipinc/hermes-operator/actions/workflows/conformance.yaml\"\u003e\u003cimg src=\"https://github.com/paperclipinc/hermes-operator/actions/workflows/conformance.yaml/badge.svg\" alt=\"Conformance\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://github.com/paperclipinc/hermes-operator/releases/latest\"\u003e\u003cimg src=\"https://img.shields.io/github/v/release/paperclipinc/hermes-operator\" alt=\"Release\"\u003e\u003c/a\u003e\n  \u003ca href=\"#supported-kubernetes-versions\"\u003e\u003cimg src=\"https://img.shields.io/badge/kubernetes-1.28--1.32-blue\" alt=\"Kubernetes versions\"\u003e\u003c/a\u003e\n  \u003ca href=\"go.mod\"\u003e\u003cimg src=\"https://img.shields.io/github/go-mod/go-version/paperclipinc/hermes-operator\" alt=\"Go version\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://api.securityscorecards.dev/projects/github.com/paperclipinc/hermes-operator\"\u003e\u003cimg src=\"https://api.securityscorecards.dev/projects/github.com/paperclipinc/hermes-operator/badge\" alt=\"OpenSSF Scorecard\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://artifacthub.io/packages/search?repo=hermes-operator\"\u003e\u003cimg src=\"https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/hermes-operator\" alt=\"Artifact Hub\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\nKubernetes operator for [nousresearch/hermes-agent](https://github.com/nousresearch/hermes-agent): a Python-based self-improving multi-platform AI agent. Declarative spec,\nopinionated security defaults, S3 backups, OCI-registry auto-update,\nSSA-based GitOps coexistence, and a one-shot migration path from\nopenclaw-operator.\n\n`hermes-operator` ships as v1.0.0 with [v1 stability commitments](docs/api-versioning.md)\nin place from day one: no v0.x grind.\n\n\u003e Inspired by [openclaw-rocks/openclaw-operator](https://github.com/openclaw-rocks/openclaw-operator);\n\u003e openclaw lessons #437, #446, #433, #471, #479, #458, #469 (and many more)\n\u003e informed concrete guardrails baked into v1. See\n\u003e [docs/superpowers/specs/2026-05-12-hermes-operator-design.md](docs/superpowers/specs/2026-05-12-hermes-operator-design.md) §1.G3.\n\n## Quickstart\n\n```bash\n# 1. Install the CRDs and operator via Helm (OCI chart; Helm 3.8+).\n#    Omit --version for the latest release, or add --version X.Y.Z to pin.\nhelm install hermes-operator \\\n  oci://ghcr.io/paperclipinc/charts/hermes-operator \\\n  -n hermes-operator --create-namespace\n\n# 2. Apply a minimal instance. The agent runs the upstream NousResearch/hermes-agent\n#    s6 image (gateway + OpenAI-compatible API server), with /health on port 8443.\nkubectl apply -n agents -f - \u003c\u003c'YAML'\napiVersion: hermes.agent/v1\nkind: HermesInstance\nmetadata:\n  name: my-hermes\nspec:\n  image:\n    repository: ghcr.io/paperclipinc/hermes-agent\n    tag: \"v0.16.0\"\n  # Point the gateway at an LLM provider and inject the key via spec.env.\n  config:\n    raw:\n      model: gpt-4o-mini\n      base_url: https://api.openai.com/v1\n  env:\n    - name: OPENAI_API_KEY\n      valueFrom:\n        secretKeyRef:\n          name: hermes-llm\n          key: apiKey\n  storage:\n    persistence:\n      enabled: true\n      size: 10Gi\nYAML\n\n# 3. Watch it converge.\nkubectl get hi -n agents -w\n# NAME        READY   PHASE   IMAGE                                AGE\n# my-hermes   True    Ready   ghcr.io/paperclipinc/hermes-agent:v0.16.0    30s\n```\n\nIf you omit `spec.config.raw.model`, the operator injects a non-routable placeholder\nso the gateway and API server still come up (and `/health` passes) without making\nlive LLM calls; inference then fails clearly until a real provider is set. Each\ninstance also gets an operator-managed random `api_server_key` (in its\n`\u003cname\u003e-gateway-tokens` Secret) that authenticates the OpenAI-compatible\n`/v1/...` API; `/health` is unauthenticated. See [Agent runtime](docs/runtime.md).\n\nFor more involved scenarios, see [`examples/`](examples/).\n\n## Architecture\n\n```mermaid\nflowchart LR\n  subgraph User\n    GitOps[FluxCD / Argo]\n    Kubectl[kubectl apply]\n  end\n\n  subgraph ControlPlane[\"Kubernetes control plane\"]\n    APIServer[(kube-apiserver)]\n    HInstance[\"HermesInstance\"]\n    HSelfConfig[\"HermesSelfConfig\"]\n    HClusterDefaults[\"HermesClusterDefaults\u003cbr/\u003e(singleton)\"]\n  end\n\n  subgraph Operator[\"hermes-operator pod\"]\n    DefaulterWebhook[Defaulter]\n    ValidatorWebhook[Validator]\n    InstanceCtrl[HermesInstance\u003cbr/\u003econtroller]\n    SelfConfigCtrl[HermesSelfConfig\u003cbr/\u003econtroller\u003cbr/\u003eSSA: hermes.agent/selfconfig]\n    ClusterDefaultsCtrl[ClusterDefaults\u003cbr/\u003econtroller]\n  end\n\n  subgraph Workload[\"agent workload (per HermesInstance)\"]\n    STS[StatefulSet]\n    Svc[Service]\n    NetPol[NetworkPolicy default-deny]\n    PVC[PVC /opt/data]\n    Honcho[Honcho Deploy\u003cbr/\u003eprofile store]\n    CronJob[Backup CronJob]\n  end\n\n  S3[(S3-compatible\u003cbr/\u003ebackup target)]\n  OCI[(OCI registry\u003cbr/\u003ehermes-agent tags)]\n\n  GitOps --\u003e APIServer\n  Kubectl --\u003e APIServer\n  APIServer \u003c--\u003e|admission| DefaulterWebhook\n  APIServer \u003c--\u003e|admission| ValidatorWebhook\n  APIServer --\u003e HInstance\n  APIServer --\u003e HSelfConfig\n  APIServer --\u003e HClusterDefaults\n  HInstance --\u003e InstanceCtrl\n  HSelfConfig --\u003e SelfConfigCtrl\n  HClusterDefaults --\u003e ClusterDefaultsCtrl\n  InstanceCtrl --\u003e STS\n  InstanceCtrl --\u003e Svc\n  InstanceCtrl --\u003e NetPol\n  InstanceCtrl --\u003e PVC\n  InstanceCtrl --\u003e Honcho\n  InstanceCtrl --\u003e CronJob\n  SelfConfigCtrl -.SSA patch.-\u003e HInstance\n  CronJob --\u003e S3\n  InstanceCtrl -.poll.-\u003e OCI\n```\n\nThe agent runs as a StatefulSet (single replica by default) under a default-\ndeny NetworkPolicy. The `HermesSelfConfig` controller uses Server-Side Apply\nunder field manager `hermes.agent/selfconfig`, so FluxCD/Argo can own the\nparent `HermesInstance` for other fields without flap. `HermesClusterDefaults`\nis a cluster-scoped singleton (name **must** be `cluster`) that fills `nil`\nfields only: explicit values on the instance always win.\n\n## Features\n\n| Area | Feature | Notes |\n|---|---|---|\n| **Declarative** | Single `HermesInstance` CR drives the whole stack | StatefulSet, Service, PVC, NetworkPolicy, ConfigMap, PDB, HPA, ServiceMonitor, Honcho deploy, backup CronJob: all owned and reconciled. |\n| **Declarative** | `HermesClusterDefaults` for cluster-wide defaults | Defaulting webhook fills `nil` fields only. |\n| **Adaptive** | `HermesSelfConfig` for audited agent-initiated mutations | SSA under field manager `hermes.agent/selfconfig`. Policy-gated by `spec.selfConfigure.protectedKeys`. |\n| **Adaptive** | OCI-registry-driven auto-update | Channel-pinned polling, pre-update backup, probe-failure rollback. |\n| **Secure** | Default-deny NetworkPolicy + per-gateway allow rules | Derived from `spec.gateways` and `spec.networking.egress`. |\n| **Secure** | Hardened container security context | The upstream s6 runtime starts as root so `/init` (PID 1) can remap the in-image user to uid/gid 1000 and chown `/opt/data`, then every service drops to uid 1000 via `s6-setuidgid`. `allowPrivilegeEscalation=false`, `fsGroup=1000`, and seccomp `RuntimeDefault` remain; `runAsNonRoot`/read-only rootfs/drop-ALL-caps are not set (s6 needs `CHOWN`/`SETUID`/`SETGID`/`DAC_OVERRIDE`/`FOWNER` and a writable `/run`). Requires an SCC that permits a root-start container (e.g. `anyuid`); incompatible with OpenShift `restricted`/`restricted-v2`. See [Agent runtime](docs/runtime.md). |\n| **Secure** | Optional Tailscale Serve sidecar | Per-instance MagicDNS hostname + Tailscale TLS cert, no LoadBalancer/Ingress. See [Tailscale Serve](#tailscale-serve). |\n| **Secure** | Per-CRD validating + defaulting webhooks | Plus warnings on unknown config keys and unresolvable gateway tokens. |\n| **Secure** | RBAC aggregation labels | `kubectl auth can-i create hermesinstances --as=jane` works out of the box. |\n| **Secure** | Image signing + SBOM | Cosign keyless OIDC, SPDX SBOM on every release. |\n| **Observable** | Prometheus metrics + ServiceMonitor | Per-controller, per-instance, per-subsystem. `metrics.secure` consistent. |\n| **Observable** | [Grafana dashboard](docs/grafana/) | Ships as JSON. Variables: `namespace`, `instance`. |\n| **Observable** | Exhaustive [condition catalogue](docs/conditions.md) | Every condition × every reason code, documented and stable. |\n| **Multi-platform** | Telegram / Discord / Slack / WhatsApp / Signal gateways | First-class `spec.gateways.*` sections, secret-rotation-friendly. |\n| **Upstream runtime** | Ships the supported NousResearch/hermes-agent s6 image | The published `ghcr.io/paperclipinc/hermes-agent` is built `FROM` the upstream image (pinned by digest). It bundles the gateway, dashboard, OpenAI-compatible API server, a Playwright/Chromium browser, node, ffmpeg, and all Python deps. No init-container venv build — the old `uv sync` / `init-apt`/`init-uv`/`init-pip` chain is gone. See [Agent runtime](docs/runtime.md). |\n| **Upstream runtime** | FFmpeg, ripgrep, browser, node available out of the box | Bundled in the upstream hermes-agent image. |\n| **Scalable** | Optional HPA via `spec.availability.hpa` | StatefulSet retained for identity through restarts. |\n| **Scalable** | Optional `topologySpreadConstraints` | Sane defaults plus `spec.availability.topologySpreadConstraints` override. |\n| **Resilient** | PodDisruptionBudget auto-managed when `replicas \u003e 1` | |\n| **Resilient** | Finalizer-driven backup-on-delete | `r.Patch` (JSON patch) for finalizer mutations, never `r.Update`. |\n| **Resilient** | Zombie-process reaper | s6-overlay `/init` as PID 1 reaps zombies; `shareProcessNamespace: false` by default (its `/init` must be PID 1). |\n| **Backup / Restore** | S3-compatible backups | Scheduled, on-delete, pre-update. `tar.zst` snapshots + `meta.json`. |\n| **Backup / Restore** | Declarative one-shot restore | `spec.restoreFrom` is immutable once applied. |\n| **Migration** | One-shot OpenClaw → Hermes migration | From sibling `OpenClawInstance` or S3 backup. Uses hermes-agent's importer. |\n| **Profile store** | Optional Honcho companion | Deployment + Service + PVC + secret, fully managed. |\n| **Gateway auth** | Per-platform `secretRef` for tokens | Rotate independently, audited via webhook warnings. |\n| **Cloud-native** | Helm chart, OLM bundle, plain kustomize manifests | All three are first-class. CRDs templated under the Helm chart. |\n| **Cloud-native** | Multi-arch (`amd64`+`arm64`), Cosign-signed, SBOM-attested | |\n| **GitOps** | SSA-based SelfConfig coexists with Argo/Flux | No flap on shared instances. |\n| **Stability** | v1.0 ships with [versioning](docs/api-versioning.md) + [deprecation](docs/deprecations.md) policies | Conversion-webhook scaffolding in place for future v2. |\n\n## Tailscale Serve\n\nSet `spec.tailscale.enabled=true` to expose the gateway on your private\ntailnet. The operator injects a `tailscale` sidecar running\n[Tailscale Serve](https://tailscale.com/kb/1312/serve): each instance gets its\nown MagicDNS hostname (`https://\u003chostname\u003e.\u003ctailnet\u003e.ts.net`) with a\nTailscale-issued TLS certificate, terminated in the sidecar and proxied to the\ngateway over localhost. No LoadBalancer or Ingress is needed. The field is\nadditive: the existing Service is unchanged.\n\n```yaml\nspec:\n  tailscale:\n    enabled: true\n    mode: serve          # only \"serve\" is implemented today\n    hostname: my-hermes  # MagicDNS hostname; defaults to metadata.name\n    authKey:\n      secretRef:\n        name: hermes-tailscale\n        key: authKey\n    # image.{repository,tag,pullPolicy} and resources are also available.\n```\n\nRequirements and notes:\n\n- **Auth key.** `authKey.secretRef` is required and must reference a\n  **reusable + ephemeral** [Tailscale auth key](https://tailscale.com/kb/1085/auth-keys).\n  Ephemeral means the node auto-removes from the tailnet when the pod stops;\n  reusable means the sidecar re-registers under the same stable hostname on\n  restart. The validating webhook rejects `enabled=true` without a\n  `secretRef` and warns when the Secret or key does not resolve.\n- **Tailnet prerequisites.** MagicDNS and HTTPS certificates must be enabled\n  on the tailnet: Serve waits for `TS_CERT_DOMAIN` and never becomes ready\n  without them.\n- **NetworkPolicy.** When the operator-managed NetworkPolicy is enabled, it\n  gains UDP egress on 3478 (STUN) and 41641 (WireGuard) for direct\n  connections. If the network blocks UDP, Tailscale falls back to DERP relays\n  over TCP/443, which the policy already allows.\n- **Reserved names.** User sidecars must not be named `tailscale`, and\n  `extraVolumes` must not be named `tailscale-serve` or `tailscale-tmp`: the\n  webhook rejects them.\n\nThe sidecar's wiring status is reported via the `TailscaleReady` condition.\nSee [`docs/api-reference.md`](docs/api-reference.md#spectailscale) for the\nfull field list.\n\n## Worked example: self-configure\n\nThe agent can persist a learned skill, env var, config patch, workspace file,\nor Honcho profile by creating a `HermesSelfConfig` in its namespace. The\noperator validates against the parent instance's `selfConfigure.protectedKeys`\nallowlist and applies via SSA:\n\n```yaml\napiVersion: hermes.agent/v1\nkind: HermesSelfConfig\nmetadata:\n  name: install-finance-skill\n  namespace: agents\nspec:\n  instanceRef: my-hermes\n  addSkills:\n    - source: \"git+https://github.com/foo/finance-skill@v1.2.0\"\n  patchConfig:\n    schedules:\n      morning-brief: \"0 8 * * *\"\n  addEnvVars:\n    - name: FINANCE_TZ\n      value: Europe/Berlin\n```\n\nApply, then watch:\n\n```bash\nkubectl get hsc -n agents\n# NAME                      PHASE     INSTANCE    AGE\n# install-finance-skill     Applied   my-hermes   3s\n```\n\nThe audit trail lives in `kubectl describe hsc install-finance-skill` and on\nthe instance via the per-field SSA field manager\n`hermes.agent/selfconfig`: `kubectl get hi my-hermes -o jsonpath='{.metadata.managedFields}'`\nshows exactly which fields the agent owns vs. Flux owns vs. you own.\n\nSee [`examples/`](examples/) for end-to-end recipes.\n\n## Supported Kubernetes versions\n\n| Operator | Kubernetes |\n|---|---|\n| v1.x | 1.28, 1.29, 1.30, 1.31, 1.32 |\n\nWe drop the oldest k8s minor when Kubernetes EOLs it, on the *next* operator\nminor release. Patch releases never change the supported matrix.\n\n## Distribution\n\n| Channel | What |\n|---|---|\n| Helm (OCI) | `helm install hermes-operator oci://ghcr.io/paperclipinc/charts/hermes-operator` |\n| OLM / OperatorHub | `kubectl operator install hermes-operator` (pending first OperatorHub release) |\n| Plain manifests | `kubectl apply -f https://github.com/paperclipinc/hermes-operator/releases/latest/download/install.yaml` |\n| Container image | `ghcr.io/paperclipinc/hermes-operator:v0.1.9` (multi-arch, Cosign-signed, SBOM attested) |\n\n## Documentation\n\n- [Design spec](docs/superpowers/specs/2026-05-12-hermes-operator-design.md): the canonical product/architecture doc.\n- [API reference](docs/api-reference.md): every field on every CR.\n- [Condition catalogue](docs/conditions.md): every status condition, reason code, troubleshooting hint.\n- [API versioning policy](docs/api-versioning.md): what is and is not a breaking change.\n- [Deprecation policy](docs/deprecations.md): the 3-step flow + active deprecations.\n- [Roadmap](ROADMAP.md): shipped, planned, future, non-goals.\n- [Examples](examples/): 9 worked YAML recipes.\n- [Grafana dashboard](docs/grafana/): operator-overview dashboard JSON.\n\n## Contributing\n\nSee [`CONTRIBUTING.md`](CONTRIBUTING.md). Pull requests follow\n[Conventional Commits](https://www.conventionalcommits.org/) (`feat:`, `fix:`,\n`docs:`, `ci:`, `chore:`, `refactor:`, `test:`); release-please drives the\nrelease-PR loop from `feat:`/`fix:`.\n\n## Security\n\nSee [`SECURITY.md`](SECURITY.md). Report vulnerabilities via the GitHub\nsecurity advisory flow; do not file public issues for security bugs.\n\n## License\n\nApache-2.0. See [`LICENSE`](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpaperclipinc%2Fhermes-operator","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpaperclipinc%2Fhermes-operator","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpaperclipinc%2Fhermes-operator/lists"}