The kars Hermes plugin is the agent-side runtime surface for kars on top of the Hermes Agent (Nous Research, MIT) — a Python 3.11+ agent harness with 20+ messaging channels, 18+ inference providers, 70+ built-in tools, and a native MCP client. When a Hermes sandbox boots, the Hermes gateway auto-discovers the kars plugin from $HERMES_HOME/plugins/kars/ and loads it; from that point on the agent's tool surface is the governance-aware kars tools the plugin registers plus the 6 Hermes built-ins kars explicitly denies.
Parity vs OpenClaw — ~all major surfaces shipped. OpenClaw's kars plugin registers 24 tools; Hermes registers 16 native + 5 via the platform MCP gateway = 21 LLM-reachable Foundry/kars tools. Surfaces:
kars_handoff_*(status / request / confirm) — implemented (handoff.py).kars_handoff_statusalways-on; the two mutation tools register only whenAGT_REGISTRY_MODE=global(same gate OpenClaw uses; in local mode the router refuses mutations).kars_mesh_transfer_file— implemented end-to-end (sender + receiver auto-save). File arrives at/sandbox/incoming/<file_name>and the LLM sees a short summary instead of the base64 blob.- Native Foundry tools:
foundry_memory,foundry_web_search,foundry_code_execute,foundry_image_generation,foundry_file_searchregister as nativefoundry_*names matching OpenClaw. The 5 operator-tier Foundry tools (conversations,evaluations,deployments,agents,download_file) reach the agent via the platform MCP server athttp://127.0.0.1:8443/platform/mcp— LLM sees them asmcp__platform__foundry_*.- Multi-agent peer roster auto-prepend — implemented (
mesh.py::_maybe_prepend_peer_roster). When 2+ siblings are tracked in the spawn roster, every outbound mesh message gets an authoritativePeer roster:block (matching OpenClaw's behaviour atagt-tools/agt.ts:545).telegram_statusagent-side tool — Hermes uses the operator-sidechannels.telegramconfig flow; documented divergence.
| Property | Value |
|---|---|
| Plugin ID | kars (manifest: runtimes/hermes/src/kars_runtime_hermes/plugin/plugin.yaml) |
| Source | runtimes/hermes/src/kars_runtime_hermes/ (~3,500 LOC Python) |
| Process / UID | Loads into the agent container (UID 1000) as the hermes gateway run --accept-hooks daemon (PID 1) |
| Network egress | None directly — every outbound call goes via the inference router (UID 1001) on 127.0.0.1:8443 |
| Mesh session ownership | This plugin owns the Signal Protocol session (X3DH + Double Ratchet + KNOCK) via runtimes/agt-mesh-python/ (kars-agt-mesh) — the Python AGT MeshClient at TS-SDK byte-for-byte parity. The router only WebSocket-bridges opaque ciphertext. |
For the conceptual split between plugin-owned mesh and router-owned governance/audit, see Architecture → The mesh and AGT boundary.
Authoritative source: each register() call across runtimes/hermes/src/kars_runtime_hermes/plugin/*.py. Verified by kubectl logs <hermes-pod> -c agent | grep "registered" on any running Hermes sandbox. The spawn, mesh, discovery, handoff, Foundry, and governance surfaces are all wired; the remaining gap vs OpenClaw is that nine of the operator-tier Foundry tools reach the agent via the platform MCP gateway (as mcp__platform__foundry_*) rather than as native foundry_* names.
| Tool | What it does |
|---|---|
kars_spawn |
Create a governed sub-agent (Hermes-runtime by default — controller stamps runtime.kind=Hermes when the parent is Hermes). |
kars_spawn_list |
Enumerate currently-running sub-agents. |
kars_spawn_status |
Pod / runtime / mesh status for one sub-agent. |
kars_spawn_destroy |
Graceful tear-down of a sub-agent. |
| Tool | What it does |
|---|---|
kars_mesh_send |
Send a message to a sibling. Encryption is via Double Ratchet inside the plugin; the router sees ciphertext only. Returns delivered_via_agt_relay (fire-and-forget) or delivered_and_replied (sync round-trip when the peer auto-responder is enabled). |
kars_mesh_inbox |
Drain the local inbox (decrypted plugin-side) without blocking. |
kars_mesh_await |
Block until a message arrives from a specific sender (with timeout). |
kars_mesh_transfer_file |
Send a file to a sibling over the encrypted mesh (sender + receiver auto-save). The file arrives at /sandbox/incoming/<file_name> and the LLM sees a short summary instead of the base64 blob (mesh.py). |
The mesh_worker background loop (KARS_MESH_AUTO_RESPONDER=1, set by the controller on sub-agent containers) auto-decrypts every inbound and dispatches to the agent's LLM, publishing the resulting reply back through kars_mesh_send.
| Tool | What it does |
|---|---|
kars_discover |
Look up sibling agents on the AGT registry by display name or capability. |
| Tool | What it does |
|---|---|
kars_handoff_status |
Check live-migration progress for this agent (always registered). |
kars_handoff_request |
Request a live handoff (migration) of this agent — creates a PENDING handoff and returns a confirmation code. |
kars_handoff_confirm |
Confirm a pending handoff with the code. |
Implemented in runtimes/hermes/src/kars_runtime_hermes/plugin/handoff.py. kars_handoff_status is always on; the two mutation tools (request / confirm) register only when AGT_REGISTRY_MODE=global — the same gate OpenClaw uses; in local-registry mode the router refuses handoff mutations. The handoff state machine itself lives in the inference router under /agt/handoff/*; the plugin forwards the agent's tool calls to it.
| Tool | Surface |
|---|---|
foundry_memory (native) |
ctx.register_tool direct — agent sees it as foundry_memory. Per-agent long-term memory backed by Azure AI Foundry Memory Store. Scoped via agent:${CLUSTER_NAME}/${SANDBOX_NAME} so memory survives pod restart and is per-sandbox-isolated. |
foundry_web_search, foundry_image_gen, foundry_code_execute, foundry_file_search, foundry_conversations, foundry_evaluations, foundry_deployments, foundry_agents, foundry_download_file |
Wired via the platform MCP server at http://127.0.0.1:8443/platform/mcp — Hermes' native MCP client connects on first use. Agent sees them as mcp__platform__foundry_* rather than foundry_*. Tracked: hermes-native-foundry-tools. |
| Tool | What it does |
|---|---|
http_fetch |
Single outbound HTTP fetch, governance-gated. Subject to the L7 egress allowlist (KarsSandbox.spec.networkPolicy.allowlistRef) + the auto-refreshing OISD + URLhaus blocklist + any active EgressApproval overlay. Hermes' own web_fetch built-in is deregistered so this is the only path. |
| Hook | What it does |
|---|---|
pre_tool_call |
AGT governance gate — every tool call is screened against the active policy profile (developer / web / azure / minimal) before the kernel executes it. Fail-closed with a 3-call grace window if the policy service is briefly unreachable. |
post_tool_call |
Telemetry — emits the standard kars OTel spans (kars.tool.invocation) so the operator-CLI topology and Headlamp mesh dashboard pick up Hermes-side tool activity identically to OpenClaw. |
Like OpenClaw, Hermes auto-prepends an authoritative Peer roster: name — role block to every outbound kars_mesh_send / kars_mesh_transfer_file when 2+ siblings are tracked in the spawn roster, so the recipient resolves sibling names unambiguously. Implemented in mesh.py::_maybe_prepend_peer_roster (matching OpenClaw's behaviour at agt-tools/agt.ts).
The plugin actively deregisters the following Hermes built-ins so the agent cannot bypass kars governance:
web_search · web_fetch · code_interpreter (Python sandbox) · image_generation · file_search (Hermes' own RAG) · chat_completion (direct provider call)
Each is replaced by its kars equivalent (foundry_* via MCP or http_fetch) that routes through the inference router and is therefore subject to Content Safety, the L7 egress allowlist, and AGT policy.
Hermes ships 20+ channel adapters; kars wires the four production-grade ones via CLI flag → env var → entrypoint.sh → hermes config set channels.* flow:
| Channel | Env var (set by CLI) | Hermes config key |
|---|---|---|
| Telegram | TELEGRAM_BOT_TOKEN, TELEGRAM_ALLOWED_USERS |
channels.telegram.{token,allowed_users,enabled} |
| Slack | SLACK_BOT_TOKEN |
channels.slack.{token,enabled} |
| Discord | DISCORD_BOT_TOKEN |
channels.discord.{token,enabled} |
WHATSAPP_TOKEN |
channels.whatsapp.{token,enabled} |
Credentials live in a Kubernetes secret named <sandbox-name>-credentials in namespace kars-<sandbox-name>, mounted via envFrom: { secretRef: { optional: true } } so a Hermes pod starts even before the secret is created. Add or rotate tokens with:
kars credentials update my-hermes-agent --telegram-token <bot-token>
kubectl rollout restart deployment/my-hermes-agent -n kars-my-hermes-agentWhen no channels are configured the entrypoint logs No channels — starting hermes gateway in idle daemon mode and serves only mesh / spawn / hook traffic — perfect for sub-agents that talk only to other agents.
Hermes ships 70+ tool plugins; kars exposes five production search/scrape providers through the same auto-config pattern as channels:
| Plugin | Env var | Hermes config key |
|---|---|---|
| Brave Search | BRAVE_API_KEY |
tools.brave.api_key |
| Tavily | TAVILY_API_KEY |
tools.tavily.api_key |
| Exa | EXA_API_KEY |
tools.exa.api_key |
| Firecrawl | FIRECRAWL_API_KEY |
tools.firecrawl.api_key |
| Perplexity | PERPLEXITY_API_KEY |
tools.perplexity.api_key |
When none are set the agent uses foundry_web_search (Foundry Bing Grounding) instead — that is the default path and requires no configuration.
Hermes pods participate in the AGT mesh identically to OpenClaw — same registry, same relay, same Signal Protocol stack — through kars-agt-mesh (runtimes/agt-mesh-python/).
| Subsystem | Where |
|---|---|
Persistent identity (Ed25519 + X25519, DID = did:mesh:<sha256(pub)[:32]>) |
$HERMES_HOME/.agt/identity.json (emptyDir, 0600) |
| Entra Verified tier upgrade | Entrypoint exchanges the projected SA token for an Entra Agent App token (audience: <app-id>/.default) → POST /agt/registry/v1/registry/verify → the operator panel and kars topology show tier=verified, verified_app_id=<guid> |
| Prekey-writer guard | An exclusive fcntl.flock on $HERMES_HOME/.agt/.mesh-prekeys.lock — a second process trying to start a MeshClient for the same identity fails loud with MeshTransportError. See the cross-runtime audit for why this matters. |
By default the image ships a smoke-test agent at /opt/kars-default-agent/main.py that answers a single chat-completion. Real users supply their own via:
spec:
runtime:
kind: Hermes
hermes:
agentCode:
oci:
image: myregistry.azurecr.io/my-hermes-agent:1.2.3
# — or —
agentCode:
git:
url: https://github.com/me/my-hermes-agent
ref: v1.2.3
path: srcThe controller mounts the source at /sandbox/agent (the Hermes working directory) — no other changes required. Hermes auto-discovers any *.py modules in the working directory; kars-registered tools and hooks remain active for everything you load.
- Runtimes — first-class adapter catalog (Hermes row included).
- CRD reference —
HermesConfig— the fullspec.runtime.hermes.*schema. - Channels & plugins — credential / env-var contract for channels and tool plugins, OpenClaw and Hermes side-by-side.
- Mesh plugin — Hermes-as-mesh-peer story with
runtimes/agt-mesh-python/. - Hermes troubleshooting runbook — operator-facing diagnostics.
- Internal: cross-runtime mesh AKS audit — what was proven and why specific defences exist.