All posts
Agent Architecture

Hermes Agent Architecture: One Core, Many Surfaces

One agent core serves a CLI, a messaging gateway, a terminal UI, and a desktop app. How the five layers fit, what a single turn does, and where new capability belongs.

Tested against Hermes Agent main branch · September 2026

Hermes meets you in different places — a terminal prompt, a chat thread, a desktop window. Underneath it is one agent core running every turn.

This post is the map we wish we had on day one: the five layers of the system, the path one request takes through a turn, and the rulebook that decides where new code belongs. Both diagrams are interactive — zoom and pan them — and each carries a text version for readers without JavaScript.

One core, four doors

The top of the map is surfaces: the CLI REPL (plus IDE and batch runners), the terminal UI and desktop app, and around twenty messaging-platform adapters. None of them contains agent logic. Each one reaches the same run_agent.py core through a session layer — the CLI drives turns in-process, the TUI and desktop app talk to a JSON-RPC backend, the dashboard embeds the real TUI instead of rebuilding it, and chat messages arrive through gateway adapters.

Below the core sit the capability edges — the tool registry, plugins, and the MCP catalog — and at the bottom the state layer: the session database, the user home directory, and the terminal backends that run shell commands.

Loading…

Loading the interactive diagram…

One agent core, many surfaces: every client reaches run_agent.py turns through a session layer, while new capability attaches at the edges — never as new core surface.
Text version of this diagram

Five layers, top to bottom. Surfaces: CLI REPL plus IDE and batch runners; TUI plus desktop app; messaging apps on platform adapters. Serving: the JSON-RPC backend hosts sessions and spawns turns; the gateway runs one turn per session behind two inbound guards; the dashboard API embeds the real TUI; the scheduler wakes session turns for cron and kanban. Agent core: the AIAgent facade iterates turn phases, streams model calls through provider adapters, and learns across sessions through memory and skills. Capability edges: the tool registry and shared core toolset, native plugins registering tools and adapters, and MCP catalog servers. State: the session database persists turns into the user home directory, and shell tools run through terminal backends.

What a turn actually does

Zoom into any arrow that says “runs turns” and you get the second diagram. An inbound event passes two guards: the adapter holds it while the session is active, and the runner intercepts control commands — stop, new, approve, deny — before they can reach a running agent. Preflight checks budgets and liveness, then the prompt is assembled.

That prompt is byte-stable for the life of the conversation, which is what keeps the provider cache warm. The model streams a response; tool calls execute under approvals and service gates, and their results re-enter the prompt for another round. When no more tool calls remain, the final response is delivered and persisted. The only thing ever allowed to rewrite the cached prefix is context compression, when the conversation outgrows the window.

Loading…

Loading the interactive diagram…

A turn is a guarded loop: two inbound guards admit the event, a byte-stable prompt protects the cache, and tool rounds repeat until a final response is delivered and persisted.
Text version of this diagram

Inbound user event → guard 1 (adapter queue holds while the session is active) → guard 2 (runner intercepts stop, new, approve, deny) → turn preflight (budget, liveness, cache scope) → prompt assembly (byte-stable system prompt) → provider call (streaming response) → tool calls? Yes: execute tools under approvals and check_fn gates, and results re-enter prompt assembly. No: final response → deliver and persist to the session database. Compression is the only cache exception: it rewrites the prompt prefix when the window fills.

The two rules that shape every change

Two invariants explain most of the design — and most review feedback:

Where things live

Each area of the codebase opens with its own guide for contributors. Working in an area means reading its guide first:

AreaStart hereCovers
Agent loopagent/AGENTS.mdTurn phases, caching integrity, compression, model resolution
CLIhermes_cli/AGENTS.mdMixins, slash dispatch, config, skins, updater, profiles
Gatewaygateway/AGENTS.mdAdapters, two guards, streaming contract, lifecycle
Toolstools/AGENTS.mdRegistry, toolsets, delegation, terminal backends
Pluginsplugins/AGENTS.mdPlugin kinds, native compat contract, in-tree policy
TUItui_gateway/AGENTS.mdProcess model, JSON-RPC transport, slash flow
Skills / Cronskills/AGENTS.md · cron/AGENTS.mdSkill frontmatter and curator; scheduler invariants, kanban

The throughline: expansive at the edges, conservative at the waist. Fix real bugs against the exact line where they manifest, prove behavior end to end rather than behind mocks, and keep every turn cache-safe.[1]

  1. Hermes Agent documentation
  2. Fallback Providers — Hermes Agent documentation
  3. Quickstart — Hermes Agent documentation

Frequently Asked Questions

How does one agent core serve a CLI, a gateway, a TUI, and a desktop app?

Every surface drives turns through the same run_agent.py core behind a session layer: the CLI drives turns in-process, the TUI and desktop app talk to the tui_gateway JSON-RPC backend, the dashboard embeds the real TUI, and messaging platforms arrive through gateway adapters. Capability is resolved per session, never from process environment.

Why is per-conversation prompt caching treated as sacred?

A long-lived conversation reuses a cached prompt prefix on every turn. Anything that mutates past context mid-conversation invalidates that cache and multiplies cost. Context compression is the only exception; slash commands that change skills, tools, or memory defer invalidation to the next session unless passed --now.

Where should a new capability be added?

Follow the footprint ladder from least to most core surface: extend existing code, then CLI command plus skill, then service-gated tool with a check_fn probe, then plugin, then MCP server in the catalog, and only as a last resort a new core tool — because every model tool ships on every API call.

What are the two inbound message guards?

While an agent is running, the base adapter queues inbound messages while the session is active, and the runner intercepts stop, new, approve, and deny before they reach the running agent. Any control command that must reach the runner has to bypass both guards.