차트 values
제공자, 통합, 고급 구성이 필요할 때는 이 기본값 위에 예제를 함께 사용하세요.
values.yaml¶
charts/hermes-agent/values.yaml
# Default values for hermes-agent.
# StatefulSet + ConfigMap (config.yaml) + Secret (.env) + Helm test.
# -- Override the chart name used in resource names.
nameOverride: ""
# -- Fully override the generated resource name (release-name-chart).
fullnameOverride: ""
image:
# -- Container image repository (multi-arch: amd64 + arm64).
repository: nousresearch/hermes-agent
# -- Image tag. Upstream uses DATE-based tags (e.g. "v2026.6.5" ==
# Hermes v0.16.0), plus `latest` / `main`. There is no semver tag.
# Empty defaults to `.Chart.AppVersion`.
tag: ""
# -- Image pull policy.
pullPolicy: IfNotPresent
# -- Image pull secrets for private registries.
imagePullSecrets: []
# -- Container command override. Empty keeps the Hermes image entrypoint, which
# starts the s6-supervised outbound messaging gateway and prepares volume
# ownership before dropping privileges. Set only for explicit debugging.
command: []
# -- Arguments passed through the image entrypoint. `gateway run` selects the
# non-interactive outbound messaging service instead of the default TUI.
args: ["gateway", "run"]
# ---------------------------------------------------------------------------
# Workload controller.
# "deployment" (default): no headless Service; when persistence is enabled, a
# single standalone PVC (named <fullname>) is created and the rollout
# strategy is forced to Recreate (so the new Pod doesn't race the old one
# for the ReadWriteOnce volume). More resource-flexible (no StatefulSet
# ordering/identity overhead): the right choice for a single replica.
# "statefulset": stable identity, a dedicated PVC via volumeClaimTemplates
# (data-<fullname>-0), and a headless Service for governance/DNS. Pick this
# if you need StatefulSet semantics (ordered rollout, stable network
# identity).
# Both render the same Pod spec (resources, env, probes, ...).
# ---------------------------------------------------------------------------
controller:
# -- Workload kind: "deployment" or "statefulset".
type: deployment
# -- Set to 0 to prepare GitOps resources (Secret, ConfigMap, PVC, ...)
# without starting an agent Pod, then scale to 1 after credentials and
# optional device login are ready. The gateway and device-login init
# container do not run while paused. Hermes Agent is a single-writer
# workload bound to one HERMES_HOME (ReadWriteOnce PVC), so values above 1
# are unsupported: Deployment replicas contend for the same volume and
# StatefulSet replicas are disconnected agent identities.
replicaCount: 1
serviceAccount:
# -- Create a ServiceAccount for the pod.
create: true
# -- Name to use; generated from fullname when empty.
name: ""
# -- Annotations to add to the ServiceAccount.
annotations: {}
# -- Mount the ServiceAccount token into the Pod. The agent does not call
# the Kubernetes API, so this chart turns it off. Behaviour change on
# upgrade: without this field, Kubernetes applies its own default of
# true. Set to true if something inside the Pod deliberately calls the
# API (e.g. kubectl-style tooling in an extraContainer).
automountServiceAccountToken: false
# ---------------------------------------------------------------------------
# config.yaml -> Hermes reads $HERMES_HOME/config.yaml as a PARTIAL OVERRIDE on
# top of its version-specific built-in defaults (precedence: CLI > config.yaml >
# .env > built-in defaults). Only set the keys you want to change here; never
# reproduce the full upstream config (it drifts across versions and Hermes fills
# the rest in). This map is rendered into a ConfigMap and seeded into HERMES_HOME
# by an init container (see `bootstrap`). Keys below use the real v2026.6.5 schema.
#
# Hermes supports many providers (openai, anthropic, google, openrouter, and any
# OpenAI-compatible endpoint). Defaults use the provider's public endpoint;
# override to point anywhere (see values-litellm.yaml / values-litellm-k8s.yaml).
# ---------------------------------------------------------------------------
config:
model:
# Model id for the chosen provider (no vendor prefix for built-ins).
default: gpt-4o-mini
# Hermes provider key. Use a BUILT-IN key: common ones:
# openai-api (api.openai.com) | anthropic | gemini | openrouter |
# nvidia | deepseek | lmstudio | ...
# NOTE: `openai` is NOT valid (it aliases to openrouter).
# For an OpenAI-compatible proxy (LiteLLM / vLLM / LM Studio / ...), register
# a custom provider under `config.providers` below and set this to that key
# (see values-litellm.yaml / values-litellm-k8s.yaml).
provider: openai-api
# Custom OpenAI-compatible providers, keyed by provider id. Empty by default.
# providers:
# litellm:
# base_url: http://litellm.my-ns.svc.cluster.local:4000/v1
# key_env: OPENAI_API_KEY # env var holding the key
# discover_models: true # populate model picker from /v1/models
providers: {}
terminal:
# Run the agent's shell/code execution INSIDE this pod. The pod itself
# (namespace, securityContext, resource limits, PVC) is the sandbox.
# The `docker` backend is intentionally NOT supported in-cluster: it needs
# a Docker daemon/socket, which is a security risk and absent on containerd
# clusters (e.g. MicroK8s / Raspberry Pi). Keep this `local`.
backend: local
agent:
# Upstream leaves `max_turns` unlimited: a hard turn cap caused silent
# mid-task truncation, so it now relies on the HERMES_MAX_ITERATIONS
# budget (500, with a wrap-up grace call) plus `gateway_timeout` below.
# Earlier chart versions seeded `max_turns: 90`, which was just a mirror
# of the then-upstream default, not a chart decision. Set it here only as
# a deliberate per-deployment cost guard.
# max_turns: 90
gateway_timeout: 1800
# -- Contents of SOUL.md, seeded into HERMES_HOME alongside config.yaml. It
# defines the agent's persistent identity. Empty means the chart seeds
# nothing, so Hermes writes its own starter file on first run.
soul:
text: ""
# Post a one-line message when the gateway has started, so you know when to
# talk to the agent. A first boot can take minutes (skill sync, model warm-up),
# and Hermes itself only announces a *restart*, not a fresh start. The chart
# seeds a `gateway:startup` hook into HERMES_HOME/hooks/ready-notify that reuses
# the bot credentials already in `env`/`extraEnvFrom`.
readyNotify:
# -- Seed the hook and enable it. Off by default.
enabled: false
# -- Where to post: `discord` (DISCORD_BOT_TOKEN + DISCORD_HOME_CHANNEL) or
# `telegram` (TELEGRAM_BOT_TOKEN + TELEGRAM_HOME_CHANNEL). Required when
# enabled, unless `auth.deviceFlow.enabled` is true: then empty follows
# `auth.deviceFlow.notify`.
notify: ""
# Seeding of config.yaml into HERMES_HOME (the persistent volume). Hermes writes
# to its home at runtime (skills, auth.json, self-improvement), so config lives
# in the writable volume rather than a read-only mount.
bootstrap:
# -- Seed chart-managed files into HERMES_HOME via an init container.
enabled: true
# -- false: seed config.yaml and configured SOUL.md only if absent, preserving
# runtime edits across upgrades. Set true to replace both files with chart
# content on every deploy.
overwrite: false
# ---------------------------------------------------------------------------
# Team mode -> one chart release still runs one independent Hermes identity,
# while several releases share one chat (a Discord thread, or a Telegram group
# or forum topic) and one RWX knowledge volume. The chart mounts one shared,
# read-only roster skill and appends a small role-specific environment hint so
# the agent knows when to load it. Cross-agent task handoffs stay visible in
# the chat; the shared volume stores only durable, accepted knowledge.
# ---------------------------------------------------------------------------
team:
# -- Enable the chart-native leader/member team protocol, roster skill, and
# shared knowledge volume mount for this release.
enabled: false
# -- Chat platform the team coordinates on: `discord` or `telegram`. It picks
# the mention format and the gates team mode enforces (see the chart README,
# "Agent team"). Every release in one team must use the same platform.
platform: discord
# -- Stable team identifier used in the generated skill and default names.
name: ""
# -- This release's team role.
role: member
# -- This release's identity. For a leader it must equal `leader.name`; for a
# member it must match one entry under `members`.
identity: ""
leader:
# -- Leader identity shared by every release in the team.
name: ""
# -- Environment variable containing the leader's Discord user ID. Supply
# it through a Secret/SealedSecret; the ID is expanded by Hermes at runtime.
mentionEnv: ""
# -- Telegram only: the leader bot's @username, without the `@`.
username: ""
# -- Configured members. ApplicationSet users define this once in the common
# template so every generated release receives the same complete roster.
members: []
# - name: researcher
# role: Research and evidence gathering
# mentionEnv: TEAM_RESEARCHER_DISCORD_ID
# capabilities: [research, source-review]
# Telegram teams give each member `username: <bot_username>` instead of
# `mentionEnv`.
protocol:
# -- Maximum serial leader-to-member handoffs before escalating to a human.
maxHandoffs: 6
skill:
# -- Mount the shared team roster and protocol as a read-only skill.
enabled: true
# -- Create the shared skill ConfigMap from this release. Set true on
# exactly one leader release; every member references the same ConfigMap.
create: false
# -- Skill name. Empty defaults to `<team.name>-roster`.
name: ""
# -- Shared ConfigMap name. Empty defaults to `<team.name>-skill`.
configMapName: ""
# -- Optional deployment-specific policy appended to the generated skill.
# Used only by the release with `skill.create=true`.
extraInstructions: ""
sharedVolume:
# -- Mount a required RWX knowledge volume when team mode is enabled.
enabled: true
# -- Create the shared PVC from this release. Set true on exactly one leader
# release; all members set false and reference the same `claimName`.
create: false
# -- Shared PVC name. Empty defaults to `<team.name>-knowledge`.
claimName: ""
# -- Mount path for durable accepted team knowledge.
mountPath: /opt/data/team-knowledge
# -- StorageClass used only when `create=true`; empty uses cluster default.
storageClass: ""
# -- RWX access modes used only when `create=true`.
accessModes: [ReadWriteMany]
# -- Requested shared storage size used only when `create=true`.
size: 10Gi
# -- Keep a chart-created shared claim when the owning release is removed.
retain: true
permissions:
# -- On the leader, chown the shared volume before Hermes starts. Enable
# only when the storage backend permits ownership changes. This init
# container needs root (see securityContext below), so it is
# incompatible with Pod Security Standards `restricted` - set false and
# rely on `podSecurityContext.fsGroup` instead when the storage backend
# honours it. See values-hardened.yaml.
enabled: false
# -- Init image used for shared-volume ownership preparation.
image: busybox:1.38
# -- Runtime owner for the shared knowledge directory.
uid: 10000
gid: 10000
# -- securityContext for the chown init container. Defaults to root -
# `chown` across arbitrary storage backends needs it. Not overridable
# to non-root; disable `permissions.enabled` instead under `restricted`.
# Setting this to `{}` does NOT restore an image-default user the way
# `auth.deviceFlow.securityContext: {}` does - it renders an explicit
# empty securityContext, which inherits podSecurityContext's fields
# (e.g. a hardened profile's non-root runAsUser), silently breaking the
# chown this container exists to perform. Disable `permissions.enabled`
# instead of clearing this value.
securityContext:
runAsUser: 0
runAsGroup: 0
# ---------------------------------------------------------------------------
# .env / secrets -> Hermes reads API keys from the environment, which take
# precedence over config.yaml. Keys are rendered into a Secret and injected via
# envFrom (not written as a .env file). Override at deploy time, e.g.
# --set-string env.OPENAI_API_KEY=sk-...
# Provider key names: OPENAI_API_KEY, ANTHROPIC_API_KEY, GOOGLE_API_KEY,
# NVIDIA_API_KEY, OPENROUTER_API_KEY, LM_API_KEY (LM Studio), ...
# ---------------------------------------------------------------------------
env:
OPENAI_API_KEY: "sk-REPLACE_ME"
# -- Plain (non-secret) env vars injected directly on the container.
extraEnv: []
# - name: HERMES_ACCEPT_HOOKS
# value: "1"
# -- Extra envFrom sources (reference existing ConfigMaps/Secrets).
extraEnvFrom: []
# - secretRef:
# name: some-existing-secret
# -- Extra volumes on the pod, for anything the agent needs as a FILE rather
# than an env var: e.g. a Secret holding a service-account JSON
# (see values-google-vertex.yaml).
extraVolumes: []
# - name: vertex-sa
# secret:
# secretName: vertex-sa
# -- Extra volume mounts on the hermes-agent container (pairs with extraVolumes).
extraVolumeMounts: []
# - name: vertex-sa
# mountPath: /var/run/secrets/vertex
# readOnly: true
# -- Extra init containers, appended after the chart's own (seed-config,
# device-flow login). Full container spec; combine with `extraVolumes` for
# one-time preparation of a user-provided volume (for example, a shared
# knowledge volume used independently of the Discord team handoff).
extraInitContainers: []
# - name: init-workspace
# image: busybox:1.38
# command: ["sh", "-c", "chown 10000:10000 /work"]
# volumeMounts:
# - name: team-workspace
# mountPath: /work
# -- Extra sidecar containers appended to the Pod's main `containers:` list.
# Distinct from `extraInitContainers` (init phase only). Full container
# spec; giving a sidecar its own resources and a PSS-compatible
# securityContext is the operator's responsibility.
extraContainers: []
# - name: log-forwarder
# image: busybox:1.38
# # Replace this command and mount with the log source your deployment uses.
# command: ["sh", "-c", "tail -F /data/application.log"]
# resources:
# requests: { cpu: 10m, memory: 16Mi }
# limits: { cpu: 50m, memory: 32Mi }
# securityContext:
# runAsNonRoot: true
# runAsUser: 65534
# allowPrivilegeEscalation: false
# readOnlyRootFilesystem: true
# capabilities: { drop: ["ALL"] }
# seccompProfile: { type: RuntimeDefault }
# volumeMounts:
# - name: data
# mountPath: /data
# readOnly: true
# -- Extra raw manifests rendered as-is alongside this chart's resources.
# Each entry is `tpl`-rendered, so `{{ .Release.Namespace }}` etc. work, and
# may be either an object or a multiline string (see examples/argocd/).
# Useful for things this chart doesn't model directly, e.g. a SealedSecret
# that a sealed-secrets controller decrypts into a Secret referenced via
# `extraEnvFrom` (see examples/argocd/).
extraResources: []
# ---------------------------------------------------------------------------
# externalSecret -> replace the chart's own env Secret (above) with an
# ExternalSecret for External Secrets Operator (ESO) users. This is a sole
# replacement, not an addition: enabling it stops `secret.yaml` from
# rendering entirely, and `env` above is then ignored - wire secret values
# solely through `data`/`dataFrom` below. Every chart-owned envFrom reference
# (main container, auth device-login init container, helm test Job) follows
# automatically, no `extraEnvFrom` needed.
#
# ESO does not guarantee restarting the Pod when the external provider
# rotates the secret's contents later - that's a reloader controller's job,
# and this chart doesn't bundle one. Pair this with a tool like Reloader or
# Stakater if that's required.
# ---------------------------------------------------------------------------
externalSecret:
# -- Render an ExternalSecret instead of the chart's own env Secret.
# Requires the External Secrets Operator CRDs to already be installed
# in-cluster.
enabled: false
# -- How often ESO resyncs the target Secret from the provider.
refreshInterval: 1h
# -- Which SecretStore/ClusterSecretStore to pull from. `name` is required
# when enabled.
secretStoreRef:
kind: ClusterSecretStore
name: ""
target:
# -- Target Secret name. Empty defaults to the chart's own env Secret
# name (`<fullname>-env`); when set, every chart-owned envFrom
# reference uses this name instead.
name: ""
creationPolicy: Owner
deletionPolicy: Retain
# -- Individual remoteRef -> key mappings. See the External Secrets
# Operator docs for the full field set.
data: []
# - secretKey: OPENAI_API_KEY
# remoteRef:
# key: hermes-agent/openai
# property: api-key
# -- Bulk provider-native secret imports. See the External Secrets
# Operator docs for the full field set.
dataFrom: []
# ---------------------------------------------------------------------------
# auth -> Interactive credential bootstrap.
#
# By default the agent authenticates with the static provider key from `env`
# (rendered into the `-env` Secret). `auth.deviceFlow` is an alternative for
# providers that support interactive device login. At pod startup it surfaces
# a verification URL + user code to a human (e.g. via the agent's Discord bot),
# waits for approval, and persists credentials where Hermes natively reads
# them. Credentials survive restarts on the persistent volume.
#
# Supported profiles are **github-copilot** (Hermes provider id `copilot`) and
# **openai-codex** (ChatGPT/Codex account login, distinct from `openai-api`).
# ---------------------------------------------------------------------------
auth:
deviceFlow:
# -- Bootstrap a provider credential via the OAuth device flow at startup.
# When false, the agent uses the static key from `env`/`extraEnvFrom`.
enabled: false
# -- Which provider profile to authenticate. Must be a key under
# `providers` below. Only one device-flow login runs at a time.
provider: github-copilot
# -- Where to deliver the verification URL + user code for human approval.
# `discord` reuses the agent's bot creds (DISCORD_BOT_TOKEN +
# DISCORD_HOME_CHANNEL from `env`/`extraEnvFrom`); `telegram` reuses
# TELEGRAM_BOT_TOKEN + TELEGRAM_HOME_CHANNEL the same way. The code is
# always also printed to the init container logs as a fallback.
notify: discord
# -- Seconds to wait for the human to authorize before the init container
# fails (and retries). Keep below the provider's device-code validity.
timeoutSeconds: 870
# -- Force a fresh login even if a token already exists on the volume.
forceRelogin: false
# -- uid/gid that should own the written token file. By default this init
# container inherits the login image's own user (root for the Python
# image below) so it can write to any storage class reliably, then
# chowns the token to this owner. Set it to the Hermes runtime uid; the
# upstream image's s6-overlay runs the agent as uid/gid 10000: so the
# non-root agent can read the credential.
tokenOwner:
uid: 10000
gid: 10000
# -- securityContext for the device-login init container. Empty by
# default - inherits the image's own user (root for the Python image,
# the pinned Hermes image otherwise). Overriding to a non-root uid only
# works if that uid can already write the token's destination path;
# see values-hardened.yaml for a verified non-root override (uid/gid
# matching tokenOwner, so the chown above becomes a same-uid no-op).
securityContext: {}
# -- Login image for GitHub-style profiles. OpenAI Codex uses the pinned
# Hermes image so auth.json persistence and refresh stay version-aligned.
image:
repository: python
tag: "3.13-slim"
# -- Resources for the login init container.
resources: {}
# Catalog of interactive-login profiles. `flow` selects the handler;
# profiles without it retain the legacy GitHub handler for compatibility.
providers:
github-copilot:
# -- Login protocol handler.
flow: github
# -- OAuth client id for the device grant. The shared opencode/Copilot-CLI
# client that Hermes upstream itself uses (hermes_cli/copilot_auth.py).
clientId: "Ov23li8tweQw6odWQebz"
# -- OAuth scope requested in the device grant.
scope: "read:user"
# -- Host serving the device-code + token endpoints (GitHub-style paths).
authHost: github.com
# -- .env key Hermes reads this provider's token from (resolution order
# COPILOT_GITHUB_TOKEN > GH_TOKEN > GITHUB_TOKEN).
tokenEnv: COPILOT_GITHUB_TOKEN
# -- Optional endpoint to verify an existing token is still live; on
# 401/403 the init container re-runs the login. Empty = skip the check.
validateUrl: https://api.github.com/copilot_internal/v2/token
openai-codex:
# -- Use the OpenAI Codex device-code flow bundled with the pinned
# Hermes version and persist refreshable credentials in auth.json.
flow: openai-codex
# -- OpenAI account issuer. Override only for a compatible test server.
issuer: https://auth.openai.com
# ---------------------------------------------------------------------------
# Persistence: HERMES_HOME state lives here.
#
# HERMES_HOME holds SQLite databases (state.db and others) in WAL mode by
# default. A PVC's access mode (RWO/RWX) says nothing about whether the
# filesystem underneath supports the file locking and shared memory WAL needs:
# prefer block-backed storage (ext4/xfs), and check what is actually mounted
# at mountPath. Hermes detects virtiofs and 9p and falls back to `delete`
# journaling for a fresh database there; it does not detect NFS/CIFS. On a
# network filesystem set `config.database.journal_mode: delete` BEFORE the
# first start. An existing WAL database is never switched live: stop the
# workload first (see the storage guide). Keep one private HERMES_HOME per
# agent; share knowledge through `team.sharedVolume`, never HERMES_HOME.
# ---------------------------------------------------------------------------
persistence:
enabled: true
# -- StorageClass for the volumeClaimTemplate. Empty = cluster default.
storageClass: ""
accessModes:
- ReadWriteOnce
size: 5Gi
mountPath: /opt/data
# -- Use an existing PVC instead of creating a new one.
# When specified, the chart will use this PVC and skip creating its own.
existingClaim: ""
# ---------------------------------------------------------------------------
# Networking. `hermes gateway run` is primarily OUTBOUND (Telegram/Discord/etc.)
# and exposes no inbound API, so no access Service is created by default. A
# headless Service is always created for StatefulSet DNS/governance.
#
# The management `dashboard`, optional OpenAI-compatible API server, and
# optional webhook receiver are inbound HTTP listeners. Enabling a listener
# does not expose it through Kubernetes: choose its Service ports explicitly
# below.
#
# The dashboard is an s6 service inside the image, off by default: set
# `dashboard.enabled: true` to start it (see the `dashboard` block below).
# Prefer that over putting `HERMES_DASHBOARD=1` in `extraEnv`/`env`: the image
# honors the bare variable, but the chart cannot see it, so none of the
# dashboard wiring applies (no readiness probe, so an Ingress answers 502 during
# the first start; no derived `public_url`/`trusted_proxies`; no auth check; no
# trusted-proxy warning). If you must keep the variable, add your own
# `probes.readiness`, for example a `tcpSocket` probe on `service.port`.
# In-container it binds
# `0.0.0.0:9119` (`HERMES_DASHBOARD_HOST` / `_PORT`), and on any non-loopback
# bind upstream's auth gate is mandatory: provide the bundled password
# provider (`HERMES_DASHBOARD_BASIC_AUTH_USERNAME` + `_PASSWORD`, secrets, so
# put them in `env`), OAuth (`HERMES_DASHBOARD_OAUTH_CLIENT_ID`) or OIDC, or
# the service fails closed and stays down. `HERMES_DASHBOARD_INSECURE` and the
# `--insecure` flag are deprecated no-ops upstream and do not disable the
# gate. Behind a TLS-terminating Ingress also set `config.dashboard.public_url`
# and `config.dashboard.trusted_proxies`; see values-ingress.yaml.
# ---------------------------------------------------------------------------
dashboard:
# -- Start the supervised management dashboard (renders `HERMES_DASHBOARD=1`).
# Exposing it also needs `service.enabled` plus an `ingress` or `httpRoute`.
# `public_url` and `trusted_proxies` are written into `config.yaml` when the
# volume is first seeded, so changing the host or `dashboard.*` later needs
# `--set bootstrap.overwrite=true` for that upgrade (see the README, "Expose the
# dashboard").
enabled: false
# -- External origin the dashboard is reached at, for example
# `https://hermes.example.com`. Feeds `config.dashboard.public_url`. When
# empty and `ingress.enabled`, it is derived from the first Ingress host
# (`https` when `ingress.tls` is set). Set it explicitly for an HTTPRoute.
# A value already set under `config.dashboard` wins.
publicUrl: ""
# -- Peers allowed to supply X-Forwarded-Proto / X-Forwarded-For, usually the
# ingress controller's pod CIDR. Feeds `config.dashboard.trusted_proxies`;
# upstream rejects unbounded entries such as 0.0.0.0/0.
trustedProxies: []
readinessProbe:
# -- Render a TCP readiness probe on `service.port` while the dashboard is
# enabled, so the pod only joins the Service once the dashboard
# listens. The first start can take minutes (bundled skills sync onto
# the volume), during which an Ingress would otherwise answer 502. An
# explicit `probes.readiness` wins. Readiness never restarts the pod.
enabled: true
# -- Seconds between readiness checks.
periodSeconds: 10
auth:
# -- Upstream auth provider the credentials below are for. The template
# fails when the provider's required keys are missing from `env` or
# `extraEnv`: `basic` (HERMES_DASHBOARD_BASIC_AUTH_USERNAME + _PASSWORD
# or _PASSWORD_HASH), `oauth` (HERMES_DASHBOARD_OAUTH_CLIENT_ID) or
# `oidc` (HERMES_DASHBOARD_OIDC_ISSUER + _CLIENT_ID). Use `external`
# when credentials arrive through `extraEnvFrom` or an ExternalSecret,
# which the chart cannot inspect.
provider: basic
apiServer:
# -- Enable Hermes' OpenAI-compatible HTTP API server.
enabled: false
# -- Bind address. Upstream defaults to 127.0.0.1; a Kubernetes Service needs
# a non-loopback address. API_SERVER_KEY is still required on loopback.
host: 0.0.0.0
# -- API server port.
port: 8642
# -- Comma-separated browser origins allowed to call the API directly. Empty
# disables browser CORS access.
corsOrigins: ""
webhook:
# -- Enable Hermes' generic inbound webhook receiver. Telegram, Discord,
# Slack, and other sources are routes behind this single listener.
enabled: false
# -- Webhook receiver port.
port: 8644
service:
# -- Create a ClusterIP Service for explicitly selected listeners.
enabled: false
# -- Service type.
type: ClusterIP
# -- Legacy dashboard Service port. Used only while `service.ports` is empty,
# preserving the existing dashboard-only Service behaviour.
port: 9119
# -- Explicit Service ports. A non-empty list replaces the legacy dashboard
# port entirely. Enabling apiServer or webhook does not add a Service port
# automatically.
ports: []
# - name: dashboard
# port: 9119
# targetPort: 9119
# - name: api-server
# port: 8642
# - name: webhook
# port: 8644
# -- Annotations to add to the Service.
annotations: {}
# Ingress and Gateway API HTTPRoute are opt-in, cluster-specific HTTP routing
# resources. Pick the one supported by your cluster. Any backend that omits a
# Service name targets this chart's Service and therefore requires
# `service.enabled: true`. Routing the management dashboard also requires
# the dashboard to be enabled with an auth provider (see the listener comment
# above and values-ingress.yaml).
ingress:
# -- Create an Ingress resource.
enabled: false
# -- IngressClass name (e.g. "nginx", "traefik"). Empty uses the cluster default.
className: ""
# -- Annotations to add to the Ingress (e.g. auth, cert-manager, rewrite rules).
annotations: {}
# -- Host/path rules. Each path defaults to this chart's Service and the
# legacy dashboard port; override `service` and `port` per listener.
hosts:
- host: hermes-agent.example.com
paths:
- path: /
pathType: Prefix
# -- Optional backend Service name. Empty targets this chart's Service.
# service: ""
# -- Optional backend Service port. Omit to use `service.port`.
# port: 9119
# -- TLS configuration for the Ingress.
tls: []
# - secretName: hermes-agent-tls
# hosts:
# - hermes-agent.example.com
httpRoute:
# -- Create a Gateway API HTTPRoute. The cluster must already provide the
# Gateway API CRD and a Gateway selected by `parentRefs`.
enabled: false
# -- Gateway API parent references.
parentRefs: []
# - name: my-gateway
# sectionName: https
# -- HTTP hostnames accepted by this route.
hostnames: []
# -- HTTPRoute rules. An empty backendRef name targets this chart's Service.
rules: []
# - matches:
# - path:
# type: PathPrefix
# value: /v1
# backendRefs:
# - name: ""
# port: 8642
# ---------------------------------------------------------------------------
# The agent runs its own shell/code execution inside this Pod (see
# `config.terminal.backend` above) - without egress control that sandbox has
# no network boundary: lateral movement to other in-cluster Services, and
# reachability of the cloud metadata endpoint (169.254.169.254 / fd00::/8),
# which on most managed clusters hands out node IAM credentials.
# ---------------------------------------------------------------------------
networkPolicy:
# -- Create a NetworkPolicy isolating both directions. Ingress is denied
# entirely by default - not an oversight: `hermes gateway run` is
# outbound-only, so nothing needs to reach this Pod unless a listener
# (dashboard, apiServer, webhook, a2a, ...) is exposed. Use
# `extraIngress` in that case.
enabled: false
# -- Permit DNS lookups to kube-dns/CoreDNS. Required for the agent to
# resolve any provider/messaging endpoint.
allowDns: true
dns:
# -- Kubernetes' immutable namespace-name label keeps this peer limited
# to kube-system. Override both selectors for a distribution whose
# DNS runs elsewhere.
namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
podSelector:
matchLabels:
k8s-app: kube-dns
# -- Block RFC1918 ranges and the cloud metadata endpoint (both IPv4
# 169.254.0.0/16 and its IPv6 equivalent within fd00::/8) while still
# permitting public internet egress. Set false when the agent must
# reach an in-cluster proxy such as LiteLLM - see
# values-networkpolicy-litellm.yaml for a precise allowlist instead.
blockPrivateEgress: true
# -- Additional raw NetworkPolicy egress rules, appended as-is.
extraEgress: []
# -- Additional raw NetworkPolicy ingress rules, appended as-is. Required
# before enabling networkPolicy alongside any exposed listener.
extraIngress: []
# -- Container resource requests/limits. Lightweight defaults aimed at small
# clusters (incl. Raspberry Pi / arm64).
resources:
requests:
cpu: "100m"
memory: "256Mi"
limits:
cpu: "2"
memory: "2Gi"
# -- Pod-level securityContext. Left empty by default to stay compatible with
# the image's s6-overlay init (which starts as root and drops privileges
# itself). Non-root and read-only rootfs are both CI-verified to work; see
# values-hardened.yaml for a Pod Security Standards `restricted`-compliant
# overlay rather than hand-rolling this.
podSecurityContext: {}
# runAsNonRoot: true
# runAsUser: 10000
# runAsGroup: 10000
# fsGroup: 10000
# seccompProfile:
# type: RuntimeDefault
# -- Container-level securityContext. Same caveat as `podSecurityContext` above.
securityContext: {}
# allowPrivilegeEscalation: false
# readOnlyRootFilesystem: true
# capabilities:
# drop: [ALL]
# -- Health probes. Empty = none. The image's s6-overlay already supervises and
# auto-restarts the gateway in-container, so k8s probes are optional. Provide a
# full probe spec to enable, e.g. an exec check:
# liveness:
# exec: { command: ["hermes","gateway","status"] }
# initialDelaySeconds: 30
# periodSeconds: 30
probes:
# -- Liveness probe spec. Empty = no liveness probe.
liveness: {}
# -- Readiness probe spec. Empty = no readiness probe.
readiness: {}
# -- Startup probe spec. Empty = no startup probe. Use this when first start
# takes longer than the liveness probe allows.
startup: {}
# -- Pod termination grace period in seconds. Empty = Kubernetes default (30s).
# The gateway (image v2026.7.1+) defaults `agent.restart_drain_timeout` to 0:
# on stop it interrupts in-flight runs immediately, persists the transcript,
# and exits fast: the default grace period is plenty. If you opt into a drain
# window via `config.agent.restart_drain_timeout: <seconds>`, raise this WELL
# ABOVE that value or the kubelet SIGKILLs the gateway mid-drain (stale lock +
# crash loop: the same race upstream warns about with systemd's
# TimeoutStopSec). See "Gateway lifecycle" in the README.
terminationGracePeriodSeconds: ""
# ---------------------------------------------------------------------------
# Helm test (chart test). `helm test <release>` runs a doctor-style Job
# (helm.sh/hook: test) AFTER install to verify the deployment. Rendered by
# default; set tests.enabled=false to skip it entirely.
# ---------------------------------------------------------------------------
tests:
# -- Render the chart test Job.
enabled: true
# -- Image used by the test Job. Empty fields fall back to the main
# `image.*` (so the hermes CLI + doctor are available and arch matches).
image:
repository: ""
tag: ""
pullPolicy: ""
# -- When true, `hermes doctor` issues fail the test. When false, doctor runs
# for visibility but only hard checks (hermes --version, seeded config) fail.
doctorStrict: false
# -- Seconds to allow `hermes doctor` to run before timing out.
doctorTimeout: 120
# ---------------------------------------------------------------------------
# Optional real model round-trip: `hermes chat -q "<prompt>"` against the
# configured provider. The full conversation (prompt + response) is printed
# to the test Job's logs. Requires a working provider key in `env`/`config`
# (e.g. config.model.provider=gemini + env.GOOGLE_API_KEY, or a LiteLLM-style
# custom provider: see values-litellm.yaml). Off by default since the
# placeholder key cannot reach a real provider.
# ---------------------------------------------------------------------------
chat:
# -- Run a `hermes chat` round-trip and log the conversation.
enabled: false
# -- Prompt sent to the agent.
prompt: "Just say hi."
# -- Max agent turns for the round-trip.
maxTurns: 1
# -- Seconds to allow each round-trip attempt to run before timing out.
timeout: 180
# -- When true, a failed/empty round-trip fails the test job.
failOnError: false
# -- Optional pool of `provider/model` ids to try in order (via `hermes chat
# -m <id> --provider config.model.provider`), each with its own `timeout`.
# Passes as soon as one succeeds: useful for free-tier models that are
# sometimes overloaded. Leave empty to use `config.model.default` as-is
# (single attempt, no `-m`/`--provider` override).
models: []
# -- Resource requests/limits for the test Job's container.
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: "1"
memory: 512Mi
# -- Annotations to add to the Pod.
podAnnotations: {}
# -- Annotations to add to the Deployment or StatefulSet object.
deploymentAnnotations: {}
# -- Labels to add to the Pod.
podLabels: {}
# -- Node selector for Pod scheduling.
nodeSelector: {}
# -- Tolerations for Pod scheduling.
tolerations: []
# -- Affinity rules for Pod scheduling.
affinity: {}
# -- RuntimeClass for the Pod. Set to a sandboxed runtime (gVisor: "gvisor",
# Kata: "kata-containers") to add a kernel isolation boundary around the
# agent's shell execution. Empty by default: the cluster's default runtime.
runtimeClassName: ""