> ## Documentation Index
> Fetch the complete documentation index at: https://docs.controlplane.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Hermes Agent

> Deploy Hermes Agent by Nous Research on Control Plane using the Template Catalog. A self-hosted, model-agnostic AI agent wrapping any LLM with persistent memory, browser automation, an OpenAI-compatible gateway API, and a web dashboard.

## Overview

Hermes Agent is a self-hosted AI agent from Nous Research. You bring the model — an API key for Anthropic, OpenAI, or any OpenAI-compatible endpoint — and the agent brings the memory, tools, and interfaces around it. This template deploys the supervised gateway with an OpenAI-compatible API for your clients and an internal-only web dashboard, with all agent state on a persistent volume.

### Architecture

* **Hermes Agent** — A single-replica stateful workload running the supervised gateway. It serves the OpenAI-compatible API on port `8642` and the web dashboard on port `9119`. Headless Chromium for browser automation is baked into the image and launched on demand.
* **Model** — External. Every completion is dispatched to your LLM provider using a key you supply; the workload holds no model weights.

### What Gets Created

* **Stateful Hermes Agent Workload** — Single replica. Gateway API on port `8642` (bearer auth, optionally public); web dashboard on port `9119` (internal only, basic auth).
* **Volume Set** — 10 GiB persistent storage at `/opt/data` for the SQLite memory database, sessions, learned skills, and agent config. Supports optional autoscaling.
* **Identity & Policy** — An identity bound to the workload with `reveal` access to exactly the one prerequisite secret, and nothing else.

<Note>
  This template does not create a GVC. You must deploy it into an existing GVC.
</Note>

## Prerequisites

Hermes Agent needs an LLM API key and a dictionary secret that you create **before** installing. No credential is passed through values.

<Steps>
  <Step title="Get an LLM API key">
    Obtain a key from your provider — Anthropic, OpenAI, or any OpenAI-compatible endpoint (OpenRouter, Ollama, vLLM, LM Studio, a proxy) reached via `provider: custom`.
  </Step>

  <Step title="Generate a gateway API key">
    Generate the bearer token your clients will present to the gateway API. It **must be at least 16 characters** — generate one with `openssl rand -hex 32`.
  </Step>

  <Step title="Create a dictionary secret">
    Create a [dictionary secret](/guides/create-secret/dictionary) holding the three values below. **Name the keys however you like** — map them with `secret.keys` at install, so an existing secret works unchanged. Pass the secret's name as `secret.name`.

    | Value                                                            | Required                      | Maps to                         |
    | ---------------------------------------------------------------- | ----------------------------- | ------------------------------- |
    | LLM API key for your provider                                    | Yes                           | `secret.keys.apiKey`            |
    | Bearer token clients present to the gateway API (≥16 characters) | Yes                           | `secret.keys.apiServerKey`      |
    | Dashboard basic-auth password                                    | When the dashboard is enabled | `secret.keys.dashboardPassword` |
  </Step>
</Steps>

<Warning>
  Hermes rejects an API server key shorter than 16 characters. The gateway still starts, but the API never serves and the workload never becomes ready.
</Warning>

Once your secret exists, install the template using your preferred method:

<CardGroup cols={2}>
  <Card title="UI" href="/template-catalog/install-manage/ui" icon="laptop">
    Browse, install, and manage templates visually
  </Card>

  <Card title="CLI" href="/template-catalog/install-manage/cli" icon="terminal">
    Manage templates from your terminal
  </Card>

  <Card title="Terraform" href="/template-catalog/install-manage/terraform" icon={<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><g fill-rule="evenodd"><path d="M77.941 44.5v36.836L46.324 62.918V26.082zm0 0" fill="#5c4ee5"/><path d="M81.41 81.336l31.633-18.418V26.082L81.41 44.5zm0 0" fill="#4040b2"/><path d="M11.242 42.36L42.86 60.776V23.941L11.242 5.523zm0 0M77.941 85.375L46.324 66.957v36.82l31.617 18.418zm0 0" fill="#5c4ee5"/></g></svg>}>
    Declare templates in your Terraform configurations
  </Card>

  <Card
    title="Pulumi"
    href="/template-catalog/install-manage/pulumi"
    icon={<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" id="Pulumi-Icon--Streamline-Svg-Logos" height="24" width="24">
    <desc>
        Pulumi Icon Streamline Icon: https://streamlinehq.com
    </desc>
    <path fill="#f26e7e" d="M4.683025 13.3318c0.869125 -0.5018 0.870575 -2.1264 0.003225 -3.62865s-2.27504 -2.313275 -3.1441725 -1.811475C0.672945 8.3935 0.6715 10.0181 1.53885 11.52035c0.86735 1.502275 2.27505 2.313275 3.144175 1.81145Zm0.0052 3.2167c0.86735 1.502275 0.865925 3.126875 -0.003225 3.628675 -0.86915 0.5018 -2.2768275 -0.309225 -3.144175 -1.81145 -0.8673525 -1.50225 -0.8659075 -3.126875 0.003225 -3.628675 0.8691325 -0.5018 2.276825 0.309225 3.144175 1.81145Zm5.922875 3.4243c0.86735 1.50225 0.8659 3.126775 -0.003225 3.62875 -0.869125 0.501775 -2.27685 -0.309325 -3.1442 -1.81155 -0.867325 -1.50225 -0.865875 -3.12685 0.00325 -3.628675 0.869125 -0.5018 2.276825 0.309225 3.144175 1.811475Zm-0.001925 -6.845275c0.86735 1.50225 0.8659 3.12685 -0.003225 3.628675 -0.869125 0.5018 -2.276825 -0.309225 -3.144175 -1.811475 -0.86735 -1.50225 -0.8659 -3.12685 0.003225 -3.62865 0.869125 -0.501825 2.276825 0.3092 3.144175 1.81145Z" stroke-width="0.25"></path>
    <path fill="#8a3391" d="M22.45775 11.524125c0.86725 -1.502225 0.865925 -3.12685 -0.003225 -3.62865 -0.869125 -0.501825 -2.276825 0.3092 -3.144175 1.811475 -0.86735 1.50225 -0.8659 3.126825 0.003225 3.62865 0.869125 0.501825 2.276825 -0.3092 3.144175 -1.811475Zm0.000175 3.2151c0.869075 0.5018 0.870625 2.1264 0.003225 3.62865 -0.86735 1.50225 -2.27505 2.313275 -3.144175 1.81145 -0.869125 -0.5018 -0.870575 -2.126425 -0.003225 -3.62865 0.86735 -1.50225 2.27505 -2.313275 3.144175 -1.81145ZM16.536225 18.157875c0.86915 0.501825 0.8706 2.126425 0.00325 3.628675 -0.86735 1.502125 -2.275075 2.313225 -3.1442 1.81145 -0.869125 -0.50175 -0.870575 -2.126425 -0.003225 -3.62865 0.867375 -1.502275 2.27505 -2.3133 3.144175 -1.811475Zm-0.003325 -6.843775c0.869125 0.5018 0.870575 2.126425 0.003225 3.628675s-2.27505 2.313275 -3.1442 1.811475c-0.869125 -0.501825 -0.870575 -2.126425 -0.003225 -3.628675 0.86735 -1.502275 2.27505 -2.313275 3.1442 -1.811475Z" stroke-width="0.25"></path>
    <path fill="#f7bf2a" d="M15.138225 2.06721c0 1.003615 -1.40625 1.817215 -3.14095 1.817215 -1.7347 0 -3.14095 -0.8136 -3.14095 -1.817215C8.856325 1.06359 10.262575 0.25 11.997275 0.25c1.7347 0 3.14095 0.81359 3.14095 1.81721ZM9.2166 5.482375c0 1.003625 -1.40625 1.8172 -3.14095 1.8172 -1.7347 0 -3.14095 -0.813575 -3.14095 -1.8172s1.40625 -1.817225 3.14095 -1.817225c1.7347 0 3.14095 0.8136 3.14095 1.817225Zm8.71005 1.8172c1.7347 0 3.14095 -0.813575 3.14095 -1.8172s-1.40625 -1.817225 -3.14095 -1.817225c-1.7347 0 -3.14095 0.8136 -3.14095 1.817225s1.40625 1.8172 3.14095 1.8172Zm-2.788425 1.605625c0 1.003625 -1.40625 1.8172 -3.14095 1.8172 -1.7347 0 -3.14095 -0.813575 -3.14095 -1.8172 0 -1.0036 1.40625 -1.8172 3.14095 -1.8172 1.7347 0 3.14095 0.8136 3.14095 1.8172Z" stroke-width="0.25"></path>
    </svg>}
  >
    Declare templates in your Pulumi programs
  </Card>
</CardGroup>

## Configuration

The default `values.yaml` for this template:

```yaml theme={null}
image: nousresearch/hermes-agent:v2026.7.7.2

model:
  provider: anthropic # anthropic | openai | custom (any other OpenAI-compatible endpoint)
  name: "" # model override (e.g. claude-opus-4.6, gpt-5); empty = provider default
  baseUrl: "" # OpenAI-compatible endpoint; required when provider is "custom"
  reasoningEffort: medium # none | low | medium | high — use "none" for non-reasoning models

secret: # the dictionary secret you created (see Prerequisites)
  name: my-hermes-secret # name of your existing dictionary secret
  keys: # point each field at the key in YOUR secret that holds it
    apiKey: api-key # LLM API key for your provider
    apiServerKey: api-server-key # bearer token clients present to the gateway API
    dashboardPassword: dashboard-password # dashboard password (used when dashboard.enabled)

dashboard:
  enabled: true # internal-only web UI on port 9119
  username: admin # basic-auth username (password comes from the secret)

resources:
  minCpu: 500m # idle floor — cpu:minCpu ratio must stay under 4:1
  minMemory: 1Gi
  cpu: 2000m # burst ceiling (headroom for on-demand browser automation)
  memory: 4Gi

volumeset:
  capacity: 10 # initial GiB (minimum 10) — memory DB, sessions, skills, config
  autoscaling:
    enabled: false # set true to auto-expand the volume as state grows
    maxCapacity: 100 # ceiling in GiB when autoscaling is enabled
    minFreePercentage: 10 # scale up when free space drops below this
    scalingFactor: 1.2 # multiplier applied on each scale-up

publicAccess:
  enabled: false # expose the gateway API (8642) on the public canonical HTTPS endpoint

internalAccess:
  type: same-gvc # none | same-gvc | same-org | workload-list
  workloads: [] # used with workload-list, e.g. //gvc/GVC/workload/NAME
```

### Model

* `image` — The Hermes Agent container image.
* `model.provider` — The LLM provider:

| Provider    | Description                                                                                                                                  |
| ----------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| `anthropic` | Anthropic's API. Requires an Anthropic key.                                                                                                  |
| `openai`    | OpenAI's API. Requires an OpenAI key.                                                                                                        |
| `custom`    | **Any other OpenAI-compatible endpoint** — OpenRouter, Ollama, vLLM, LM Studio, or a proxy. Requires `model.baseUrl` and that service's key. |

* `model.name` — Model override, e.g. `claude-opus-4.6`, `gpt-5`. Empty uses the provider default. Recommended for non-Anthropic providers.
* `model.baseUrl` — The OpenAI-compatible endpoint. Required when `provider` is `custom`.
* `model.reasoningEffort` — `none`, `low`, `medium`, or `high` (default `medium`).

To reach OpenRouter or any other OpenAI-compatible service, use `provider: custom`:

```yaml theme={null}
model:
  provider: custom
  baseUrl: https://openrouter.ai/api/v1
  name: anthropic/claude-sonnet-4-5
```

<Warning>
  Hermes sends a reasoning effort with every request, and models that do not support reasoning reject it with `400: Unsupported parameter: 'reasoning.effort'`. Set `reasoningEffort: none` for those (e.g. `gpt-4o`); leave the default for reasoning-capable models (e.g. `gpt-5`, `claude-opus-4.6`).
</Warning>

<Note>
  `model.baseUrl` has no effect with `provider: anthropic` — the Anthropic client ignores it. The template fails at render if you set both, so use `provider: custom` to route through a proxy.
</Note>

### Secret

* `secret.name` — Name of the dictionary secret you created. See [Prerequisites](#prerequisites).
* `secret.keys.apiKey` / `secret.keys.apiServerKey` / `secret.keys.dashboardPassword` — The key names **within your secret** that hold each value. Override these to match an existing secret rather than renaming its keys.

### Dashboard

* `dashboard.enabled` — Serve the web dashboard on port `9119`. Internal only — it is never published on the public endpoint.
* `dashboard.username` — Basic-auth username. The password comes from your secret.

### Resources

* `resources.minCpu` / `resources.minMemory` — The idle floor.
* `resources.cpu` / `resources.memory` — The burst ceiling, giving headroom for the on-demand headless browser.

The min→max spread is the elasticity: the workload idles at the floor and bursts toward the ceiling only while browser automation runs. Keep `cpu` under 4× `minCpu` — the platform rejects a wider ratio.

### Storage

* `volumeset.capacity` — Initial volume size in GiB (minimum 10) for the memory database, sessions, skills, and config.
* `volumeset.autoscaling.enabled` — Automatically expand the volume as state grows. When enabled:
  * `maxCapacity` — Maximum volume size in GiB.
  * `minFreePercentage` — Trigger a scale-up when free space drops below this percentage.
  * `scalingFactor` — Multiply the current capacity by this factor when scaling up.

### Access

* `publicAccess.enabled` — Expose the gateway API on the public canonical HTTPS endpoint. Defaults to `false`. Read the security note below before enabling.
* `internalAccess.type` — Internal firewall scope of the workload:

| Type            | Description                                                            |
| --------------- | ---------------------------------------------------------------------- |
| `none`          | No internal access.                                                    |
| `same-gvc`      | Allow access from all workloads in the same GVC.                       |
| `same-org`      | Allow access from all workloads in the same organization.              |
| `workload-list` | Allow access only from workloads listed in `internalAccess.workloads`. |

<Warning>
  **`publicAccess.enabled: true` publishes a terminal-capable agent to the internet**, guarded only by your bearer token. The agent's terminal backend runs unsandboxed as the container user with full file access, so anyone holding the key can execute work inside the workload. It is off by default — before enabling it, use a long random API server key (`openssl rand -hex 32`) and prefer restricting reach via `internalAccess`.
</Warning>

## Connecting

| Interface                       | Where                                                                                                                                                                                                                                    | Auth                                                                        |
| ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- |
| Gateway API (OpenAI-compatible) | From another workload: `{release}-hermes-agent.{gvc}.cpln.local:8642`. With `publicAccess.enabled: true`, also on the canonical HTTPS endpoint — read `status.canonicalEndpoint` from `cpln workload get {release}-hermes-agent -o yaml` | Bearer token (`secret.keys.apiServerKey`)                                   |
| Web dashboard                   | Internal only — `cpln workload port-forward {release}-hermes-agent --gvc {gvc} -p 9119:9119`, then `http://localhost:9119`                                                                                                               | Basic auth (`dashboard.username` + the dashboard password from your secret) |

Example request against the gateway API:

```bash theme={null}
curl https://ENDPOINT/v1/chat/completions \
  -H "Authorization: Bearer $API_SERVER_KEY" \
  -H "Content-Type: application/json" \
  -d '{"messages":[{"role":"user","content":"hello"}]}'
```

## Messaging Platforms

Hermes supports chat-platform gateways such as Telegram, Discord, and Slack. These are **configured after install** using Hermes's own interactive setup — not through this template's values:

```bash theme={null}
cpln workload exec {release}-hermes-agent --gvc {gvc} --container hermes -- hermes gateway setup
```

Follow the prompts for your platform; the configuration is stored on the data volume. See the [Hermes Agent documentation](https://github.com/NousResearch/hermes-agent) for each platform's requirements, such as bot tokens.

## Important Notes

* **The API server key must be at least 16 characters** — Hermes rejects anything shorter, and the workload will not become ready.
* **The dashboard is internal-only** — it is never on the public endpoint; reach it via `cpln workload port-forward`.
* **Single replica by design** — memory is a single-writer SQLite database and upstream forbids two gateways sharing one data directory. Do not scale up. State persists on the volume across restarts and the agent resumes; only in-flight work is lost.
* **The model is external** — cost and rate limits are governed by your LLM provider, not this workload.
* **Failed model calls return HTTP 200** with the error inside the body (`"finish_reason": "error"`, `"hermes": {"failed": true}`). A client that checks only the HTTP status will read a provider failure as success — inspect the body, or the agent log at `/opt/data/logs/agent.log`.
* **Keep `cpu` under 4× `minCpu`** — the platform rejects a wider ratio; raise `minCpu` if you raise `cpu`.
* **Resetting requires an uninstall** — changing the secret and redeploying does not wipe existing memory or config on the volume. `cpln helm uninstall` deletes the volume set.

## External References

<CardGroup cols={2}>
  <Card title="Hermes Agent Documentation" icon="book" href="https://github.com/NousResearch/hermes-agent/blob/main/README.md">
    Official Hermes Agent documentation
  </Card>

  <Card title="Hermes Agent Repository" icon="github" href="https://github.com/NousResearch/hermes-agent">
    Source, issues, and release notes
  </Card>

  <Card title="Nous Research" icon="flask" href="https://nousresearch.com/">
    The team behind Hermes Agent
  </Card>

  <Card title="Hermes Agent Template" icon="github" href="https://github.com/controlplane-com/templates/tree/main/hermes-agent">
    View the source files, default values, and chart definition
  </Card>
</CardGroup>
