Use promo code BETATEST1 for full access
seekrit
Docs/Hermes Agent

Hermes Agent

Hermes Agent reads its credentials — model providers, tool APIs, messaging platforms — from ~/.hermes/.env and the process environment. A secret source fills that environment from somewhere else at startup, before Hermes reads any of it, and that is where seekrit fits.

pip install seekrit
hermes plugins enable seekrit
seekrit hermes init

init prints the block to merge into ~/.hermes/config.yaml, plus the two steps around it. Once it is in place, .env holds one thing — the service token — and every other credential arrives decrypted at startup.

Two sources, and the choice matters

pip install seekrit registers both. Neither does anything until a config section enables it.

SourceShapeUse it when
seekritbulk — one environment, wholeThe usual case. Nothing to enumerate, and a secret added later appears without a config edit.
seekrit_refsmapped — explicit VAR: skt://NAMEYou need to rename a secret, take only some of an environment, read more than one environment, or win a contested variable.

The distinction is not cosmetic: Hermes lets a mapped claim beat a bulk one on a variable both sources offer. If seekrit is your store of record and you also run another source, mapped is how you make sure seekrit wins.

Bulk

secrets:
  sources: [seekrit]
  seekrit:
    enabled: true

Optional keys: include and exclude narrow which names are contributed, token_env names a different variable to read the token from, overrides pulls a different environment slice of a composed group, api_url and timeout_seconds do what they say.

note

include and exclude are a tidiness tool, not a boundary. The token still resolves the whole environment, so a secret that must not reach this process needs a narrower token — not an exclude.

Mapped

secrets:
  sources: [seekrit_refs]
  seekrit_refs:
    enabled: true
    tokens:
      billing: SEEKRIT_TOKEN_BILLING
    env:
      OPENAI_API_KEY: skt://OPENAI_KEY
      STRIPE_SECRET_KEY: skt://billing/STRIPE_SECRET_KEY

A reference is skt://NAME, or skt://<token-alias>/NAME to resolve through a second token. A tokens: entry names an environment variable, never a token value — config.yaml is not a place for a credential, and Hermes' own docs say the same.

seekrit hermes init will write the whole block for you:

seekrit hermes init --mapped \
  --bind OPENAI_API_KEY=OPENAI_KEY \
  --bind STRIPE_SECRET_KEY=billing/STRIPE_SECRET_KEY \
  --token billing=SEEKRIT_TOKEN_BILLING

Why a reference names a token and not an app and environment: a skt_ service token is bound to exactly one environment, so there is nothing on the read path that could resolve an arbitrary app/env pair. An skt://app/env/NAME spelling would be a reference that always failed.

Precedence

Hermes owns this, and it is worth knowing before you debug a value that is not what you expected. In order:

  1. A protected bootstrap token always wins. Both sources declare every variable they read a token from — SEEKRIT_TOKEN and anything under tokens: — so nothing can overwrite the credential the next startup needs.
  2. A mapped claim beats a bulk claim on a contested variable.
  3. The first source in sources: claims a variable; a later source contributing the same name gets a conflict warning, not the variable.
  4. Within a source, override_existing decides against .env and your shell. Both seekrit sources default it to true, matching Hermes' bundled sources: seekrit is the store of record, so after a rotation the new value has to beat whatever a stale .env still holds.

That last default is the one that surprises people. When a local override is the point — a variable you set in your shell to test something — name it under preserve_existing and it always wins:

secrets:
  preserve_existing: [OPENAI_API_KEY]

The token

Put it in ~/.hermes/.env, which is what that file is for:

SEEKRIT_TOKEN=skt_…

Scope it to exactly the environment this agent needs — see service tokens. A Hermes agent has broad reach by design, so the token is the ceiling that matters.

When something fails

A secret source may not raise, so failures come back as an error kind that Hermes reports at startup with a remediation hint. What each means here:

KindMeans
NOT_CONFIGUREDThe source is enabled but SEEKRIT_TOKEN is not set — or, for the mapped source, env: is empty.
AUTH_FAILEDseekrit rejected the token, or the token cannot decrypt this environment. Check its key grant with seekrit access list.
REF_INVALIDA reference is malformed, names a token alias that is not under tokens:, or points at a secret that does not exist.
NETWORKThe seekrit API was unreachable, rate-limited, or returned a 5xx.
EMPTY_VALUEThe secret exists and holds an empty string. Neither source will apply "" over a working credential.
TIMEOUTRaise timeout_seconds for the source.

A mapped source fails whole, not partially: a binding is an explicit statement that this variable comes from seekrit, so half a map is a config error worth surfacing at startup rather than an agent that mostly works.

Tools, as well as credentials

Filling the environment is one half. Hermes also discovers MCP servers at startup and registers their tools alongside its built-ins, so the agent can manage secrets as well as use them:

mcp_servers:
  seekrit:
    command: npx
    args: ["-y", "@seekrit/mcp"]
  seekrit-cloud:
    url: https://mcp.seekrit.dev/mcp

The local server holds your key and does the crypto; the hosted one is zero-knowledge and never returns a value. See AI agents.

Without the plugin

If installing a Python package into the Hermes environment is not an option, Hermes ships a generic command source that reads KEY=VALUE lines from any helper — and seekrit export already emits exactly that:

secrets:
  sources: [command]
  command:
    enabled: true
    command: ["seekrit", "export", "--format", "dotenv"]

This is a weaker arrangement than the real source: no per-variable provenance, no reference syntax, no protected bootstrap token, and a shell-out per startup. Use it as a stopgap.

What this does and does not protect

Filling the environment keeps plaintext out of ~/.hermes/.env, and takes the credentials out of a file that gets copied into containers, backed up, and occasionally pasted into a chat. It does not stop the agent from reading a value: the secrets are in os.environ of the process the model drives, and Hermes says as much about its own sources.

RequirementUse
No plaintext at rest on diskthis page
The agent never holds the key at allegress proxy — Hermes gets a {{seekrit:NAME}} placeholder
Bound to specific hosts, paths, and methodsagent access policy

The two compose well: resolve the proxy's own credential through the secret source, and give Hermes placeholders for every upstream behind it.

Troubleshooting

SymptomCause
Hermes never mentions the source at startupIt is registered but not enabled — secrets.seekrit.enabled: true, and hermes plugins enable seekrit.
HERMES_PLUGINS_DEBUG=1 shows no seekrit pluginThe seekrit package is not installed in the environment Hermes runs in. Scaffold the directory install instead: seekrit hermes init --plugin-dir.
A variable holds a stale valueAnother source claimed it first, or preserve_existing names it. Check the (from …) provenance label Hermes prints.
Credentials work in the CLI but not in HermesHermes runs from ~/.hermes, not your shell — the token must be in .env, not just exported in one terminal.

Reference