コンテンツにスキップ

Deploying with ArgoCD

These Application manifests deploy hermes-agent via ArgoCD safely, including multiple instances in the same namespace without collisions.

Each file mirrors the corresponding charts/hermes-agent/values-*.yaml example 1:1, with secrets wired via extraEnvFrom instead of plain --set:

Application file Mirrors Required Secret
hermes-agent.yaml chart defaults (values.yaml) hermes-agent-secrets (OPENAI_API_KEY)
hermes-agent-openai.yaml values-openai.yaml hermes-agent-openai-secrets (OPENAI_API_KEY)
hermes-agent-openai-sealedsecret.yaml values-openai.yaml + GitOps hermes-agent-openai-sealedsecret-secrets via SealedSecret (extraResources)
hermes-agent-anthropic.yaml values-anthropic.yaml hermes-agent-anthropic-secrets (ANTHROPIC_API_KEY)
hermes-agent-gemini.yaml values-gemini.yaml hermes-agent-gemini-secrets (GOOGLE_API_KEY)
hermes-agent-upstage.yaml values-upstage.yaml (model overridden to Solar Open 2) hermes-agent-upstage-secrets (UPSTAGE_API_KEY)
hermes-agent-openrouter.yaml values-openrouter.yaml hermes-agent-openrouter-secrets (OPENROUTER_API_KEY)
hermes-agent-litellm.yaml values-litellm.yaml hermes-agent-litellm-secrets (OPENAI_API_KEY, proxy key)
hermes-agent-litellm-k8s.yaml values-litellm-k8s.yaml hermes-agent-litellm-k8s-secrets via SealedSecret (extraResources)
hermes-agent-anthropic-and-discord.yaml values-anthropic-and-discord.yaml hermes-agent-anthropic-discord-secrets (ANTHROPIC_API_KEY, DISCORD_BOT_TOKEN)
hermes-agent-openai-and-telegram.yaml values-openai-and-telegram.yaml hermes-agent-openai-telegram-secrets (OPENAI_API_KEY, TELEGRAM_BOT_TOKEN)
hermes-agent-nvidia-nim-and-discord.yaml values-nvidia-nim-and-discord.yaml hermes-agent-nim-discord-secrets (NVIDIA_API_KEY, DISCORD_BOT_TOKEN)
hermes-agent-nvidia-nim-and-discord-sealedsecret.yaml values-nvidia-nim-and-discord.yaml + GitOps hermes-agent-nim-discord-sealedsecret-secrets via SealedSecret (extraResources, NVIDIA_API_KEY + DISCORD_BOT_TOKEN)
hermes-agent-github-copilot.yaml values-github-copilot.yaml + GitOps hermes-agent-copilot-secrets via SealedSecret (DISCORD_BOT_TOKEN only: Copilot token minted at runtime via OAuth device flow)
hermes-agent-ingress.yaml values-ingress.yaml hermes-agent-ingress-secrets (OPENAI_API_KEY + the dashboard username, password and session secret; the dashboard's own password provider, for a trusted network or VPN)
hermes-collab-pair.yaml values-multi-agent-collab.yaml (×2: planner+builder) hermes-planner-discord-secrets + hermes-builder-discord-secrets: a collaborating pair that hands off by @mention; see Hermes collaboration
hermes-team.yaml values-team-leader.yaml + values-team-member.yaml hermes-team-routing + one private Secret per bot: one ApplicationSet generates the leader and members; its leader creates the shared skill ConfigMap and RWX knowledge PVC exactly once; see Hermes teams

hermes-agent.yaml is the bare-minimum starting point - pure chart defaults plus the secret wiring; copy it and add a valuesObject to customize.

hermes-collab-pair.yaml is the first multi-Application example: two agents (a planner on LiteLLM and a builder on Copilot device-flow) sharing one Discord channel and handing off by @mention. See Hermes collaboration for the handoff protocol and the four loop-brake env vars.

hermes-team.yaml scales that pattern up with one ApplicationSet for the team leader (august) and members (may, march). The roster and shared skill are declared once in the common template; list entries carry only per-agent identity, role, and Secret name. The leader-generated release creates the shared skill ConfigMap and RWX PVC exactly once, while members reference both read-only. The agents share a Discord channel (star topology: mentions flow leader ↔ member only). The thread is the coordination bus and audit log. The PVC stores durable reusable knowledge but never tasks, status, intermediate results, or completion signals. See Hermes teams → "Leader-orchestrated teams".

All examples use the OCI registry source form (repoURL/chart/ targetRevision pointing at ghcr.io). A Git source form (repoURL/targetRevision/path) works too if you'd rather track a chart checked into a Git repo - swap freely between the two.

hermes-agent-litellm-k8s.yaml is the most complete example: it demonstrates the full GitOps pattern (SealedSecret via extraResources + extraEnvFrom + non-default persistence.storageClass). The others use a plain Secret created out-of-band (see each file's header comment for the exact kubectl create secret command).

All Applications target destination.namespace: hermes-agent and use distinct releaseNames - they can be applied together in the same namespace without colliding (see below).

The one rule: unique fullname per instance

Every chart resource is named from the Helm fullname ({fullname}, {fullname}-0, {fullname}-config, {fullname}-env, {fullname}-headless, {fullname}-test, data-{fullname}-0). Two Applications collide only if they render the same fullname in the same namespace.

So give each Application a distinct spec.source.helm.releaseName, and:

Set metadata.name == spec.source.helm.releaseName.

This does two things: 1. Makes the fullname unique per instance (no name collisions). 2. Makes the chart's app.kubernetes.io/instance value equal the Application: intuitive and consistent.

Why there is no tracking-label clash here

ArgoCD tracks ownership with a label whose key is set by application.instanceLabelKey. This cluster uses argocd.argoproj.io/instance (the ArgoCD-specific key), while the chart uses app.kubernetes.io/instance for its own selectors. Different keys → no clash, even with the default label tracking method. (If a cluster instead used app.kubernetes.io/instance for tracking, switch it to annotation tracking: application.resourceTrackingMethod: annotation in argocd-cm.)

Secrets (do not commit keys)

Keep the chart's env.<KEY> values as placeholders and inject the real key(s) from a Secret created out-of-band (or via sealed-secrets / external-secrets). Each Application's header comment has the exact command, e.g.:

kubectl create namespace hermes-agent
kubectl create secret generic hermes-agent-openai-secrets -n hermes-agent \
  --from-literal=OPENAI_API_KEY='sk-<your-key>'

extraEnvFrom references that Secret; since it is applied after the chart's own env Secret, its values win.

SealedSecret walkthrough (NVIDIA NIM + Discord)

hermes-agent-nvidia-nim-and-discord-sealedsecret.yaml is the GitOps variant of hermes-agent-nvidia-nim-and-discord.yaml: instead of creating a plain Secret out-of-band, it ships a bitnami SealedSecret in extraResources. The sealed-secrets controller decrypts it in-cluster into a regular Secret (hermes-agent-nim-discord-sealedsecret-secrets), which extraEnvFrom then mounts. The encrypted blob is safe to commit to Git - only the controller's private key (held by your cluster) can decrypt it.

This walkthrough seals two keys at once (NVIDIA_API_KEY and DISCORD_BOT_TOKEN). For a single key, see the simpler kubeseal --raw --from-file form used in hermes-agent-openai-sealedsecret.yaml.

Prerequisites: - The sealed-secrets controller is installed in the target cluster (e.g. helm install sealed-secrets sealed-secrets/sealed-secrets -n kube-system). - The kubeseal CLI is installed locally, matching (or able to fetch) the controller's public certificate.

1. Write a plaintext Secret manifest - do not apply it:

cat > /tmp/hermes-agent-nim-discord-secret.yaml <<'EOF'
apiVersion: v1
kind: Secret
metadata:
  name: hermes-agent-nim-discord-sealedsecret-secrets
  namespace: hermes-agent
type: Opaque
stringData:
  NVIDIA_API_KEY: "nvapi-<your-real-key>"
  DISCORD_BOT_TOKEN: "<your-real-bot-token>"
EOF

2. Seal it. kubeseal -o yaml reads a Secret manifest and emits the SealedSecret equivalent, encrypting every entry in data/stringData with the controller's public key (fetched automatically from the cluster, or pass --cert <pub-cert.pem> for an offline cert):

kubeseal --scope namespace-wide \
  -o yaml < /tmp/hermes-agent-nim-discord-secret.yaml \
  > /tmp/hermes-agent-nim-discord-sealedsecret.yaml

This produces a SealedSecret with spec.encryptedData.NVIDIA_API_KEY and spec.encryptedData.DISCORD_BOT_TOKEN, each a long AgB... / AgD... base64 blob. --scope namespace-wide lets the SealedSecret be renamed/moved within hermes-agent without re-sealing - matches the convention used by the other SealedSecret examples in this directory.

3. Splice the two encryptedData values into hermes-agent-nvidia-nim-and-discord-sealedsecret.yaml's extraResources[0].spec.encryptedData, replacing the AgDUMMY_replace_with_kubeseal_output== placeholders:

extraResources:
  - apiVersion: bitnami.com/v1alpha1
    kind: SealedSecret
    metadata:
      name: hermes-agent-nim-discord-sealedsecret-secrets
    spec:
      encryptedData:
        NVIDIA_API_KEY: AgB...      # <- from step 2
        DISCORD_BOT_TOKEN: AgD...   # <- from step 2
      template:
        metadata:
          name: hermes-agent-nim-discord-sealedsecret-secrets
        type: Opaque

Also fill in real values for extraEnv[].value (DISCORD_HOME_CHANNEL, DISCORD_ALLOWED_USERS) and pick an NVIDIA NIM model your account can reach.

4. Apply the Application and verify:

kubectl apply -f examples/argocd/hermes-agent-nvidia-nim-and-discord-sealedsecret.yaml

# the controller should decrypt the SealedSecret into a Secret:
kubectl get sealedsecret,secret -n hermes-agent hermes-agent-nim-discord-sealedsecret-secrets

# and the pod should pick up both keys via extraEnvFrom (fullnameOverride:
# hermes-agent keeps resource names short: see the file's valuesObject):
kubectl exec -n hermes-agent deploy/hermes-agent -- \
  env | grep -E '^(NVIDIA_API_KEY|DISCORD_BOT_TOKEN)='

If kubectl get secret shows no Secret, check the controller's logs (kubectl logs -n kube-system -l app.kubernetes.io/name=sealed-secrets) - the most common cause is sealing with the wrong cluster's certificate.

Apply

kubectl apply -f examples/argocd/hermes-agent-openai.yaml

(Swap in any other file from the table above.)

Multiple instances in the same namespace

Just duplicate the Application with a different name == releaseName. Example second instance alongside the first, both in hermes-agent:

metadata:
  name: hermes-agent-staging      # different name
spec:
  source:
    helm:
      releaseName: hermes-agent-staging   # == metadata.name
  destination:
    namespace: hermes-agent       # SAME namespace is fine

Resources render as hermes-agent-staging-* (pod hermes-agent-staging-0, hermes-agent-staging-config, data-hermes-agent-staging-0, …) - no overlap with the hermes-agent-* set. Each gets its own PVC, so the two instances do not share a knowledge base.