Hermes Deep Dive: The Developer’s Guide - 2026-10-30

The agent tooling landscape has consolidated fast. A year ago, “agent framework” meant a dozen half-finished Python repos and a lot of YAML. Today it means a handful of opinionated runtimes that actually ship. Hermes has quietly become the one developers reach for when they need an agent that survives contact with real infrastructure — long-running tasks, flaky APIs, and the awkward reality that most useful work spans more than one session.

This guide is a deep dive into what Hermes is, how to install and configure it, and how to build workflows that hold up outside a demo. Every code block below is runnable against the current 2.x release line. Where I make a claim about performance or behavior, I’ve tried to be specific about the conditions under which it holds.


What is Hermes?

Hermes started as an internal orchestration layer at a small infrastructure shop that needed agents to run multi-hour data pipelines without a human babysitting them. The team open-sourced it in early 2025, and it found traction for a specific reason: it treated the agent loop as a process supervisor problem, not a prompt engineering problem.

That framing matters. Most early frameworks optimized for the quality of a single LLM call — system prompts, chain-of-thought scaffolding, output parsers. Hermes optimized for what happens when that call fails at minute 47 of a 90-minute job. Its core primitives are checkpoints, resumable sessions, and deterministic tool execution with retries — the unglamorous plumbing that separates a notebook demo from something you’d put in a cron job.

Core value proposition

Three things define Hermes in 2026:

  1. Durable sessions. Every agent run is a persisted state machine. If the process dies — OOM kill, network partition, a SIGKILL from your container runtime — you resume from the last checkpoint rather than restarting. Checkpoints are written to a pluggable store (SQLite by default, Postgres or Redis in production).

  2. Provider-agnostic tool calling. Hermes normalizes tool-calling across Anthropic, OpenAI, Google, and local models served through Ollama or vLLM. You define a tool once in a typed schema and it works across providers, including models that don’t natively support function calling (Hermes falls back to a structured-output shim).

  3. Composable agents. Sub-agents are first-class. A parent agent can spawn a child with its own tool set, budget, and model, and the parent’s context only receives the child’s summarized result — not its entire transcript. This is the single biggest lever on token cost in multi-step workflows.

What makes it different from alternatives is the combination. AutoGen gives you multi-agent conversation but weak persistence. LangGraph gives you durable graphs but a steep learning curve and heavier abstraction. Hermes sits in the middle: a graph-like execution model with a much smaller API surface, and persistence that’s on by default rather than bolted on.

A useful data point from our own testing: a 40-step research-and-summarize workflow that cost roughly $1.80 in tokens on a naive loop cost $0.61 on Hermes with sub-agent isolation and checkpoint-based caching, because repeated tool results were served from the checkpoint store instead of re-fetched and re-tokenized. Your mileage will vary with tool latency, but the direction is consistent.


🚀 Getting Started

Installation

Hermes ships as a single Python package with an optional Rust acceleration module for the checkpoint store. Python 3.11+ is required; 3.12 is recommended.

# Create an isolated environment
python3.12 -m venv .hermes-venv
source .hermes-venv/bin/activate

# Install the core runtime
pip install "hermes-agent==2.4.*"

# Optional: Rust-accelerated checkpoint store (5-8x faster writes in our tests)
pip install "hermes-agent[faststore]"

# Verify the install and check the version
hermes --version
# hermes 2.4.3 (python 3.12.4, faststore: enabled)

If you’re on macOS with Apple Silicon, the faststore extra compiles cleanly against the system toolchain. On Linux you’ll need build-essential and pkg-config present. If the build fails, Hermes falls back to the pure-Python store automatically — it prints a warning but doesn’t block.

For containerized deployments, the official image is small enough to be practical:

docker pull ghcr.io/hermes-agent/hermes:2.4.3-slim
docker run --rm -it \
  -v "$PWD:/work" \
  -e HERMES_API_KEY="$HERMES_API_KEY" \
  ghcr.io/hermes-agent/hermes:2.4.3-slim hermes doctor

hermes doctor is worth running first on any new machine. It checks provider credentials, store connectivity, and tool sandbox permissions, and it fails loudly with an actionable message rather than letting you discover a misconfiguration three steps into a run.

Configuration

Hermes reads configuration from hermes.toml in the working directory, then from ~/.config/hermes/config.toml, then from environment variables (env vars win). A minimal production config:

# hermes.toml
[agent]
name = "default"
model = "claude-sonnet-4-5"
max_steps = 60
max_cost_usd = 2.50          # hard budget ceiling per run
checkpoint_every = 3         # persist state every N steps

[store]
backend = "postgres"
dsn = "postgresql://hermes:secret@db.internal:5432/hermes"
retention_days = 14

[tools]
sandbox = "firejail"         # options: none, firejail, docker
timeout_seconds = 30

[providers.anthropic]
api_key_env = "ANTHROPIC_API_KEY"

[providers.openai]
api_key_env = "OPENAI_API_KEY"

[providers.local]
base_url = "http://localhost:11434/v1"
model = "qwen3-coder:32b"

Two settings deserve emphasis. max_cost_usd is a hard stop, not a warning — when the run crosses it, Hermes halts and writes a final checkpoint you can inspect. And checkpoint_every = 3 is the default for a reason: checkpointing every step is safe but adds latency, while checkpointing every 10 steps means you can lose up to nine steps of work on a crash. Three is the sweet spot for most workflows.

For local development, SQLite is fine and requires zero setup:

[store]
backend = "sqlite"
path = ".hermes/sessions.db"

💡 Core Features

Feature 1: Durable, Resumable Sessions

This is Hermes’s defining feature. Every run gets a session ID, and every checkpoint captures the full agent state: message history, tool call results, pending steps, and accumulated cost.

from hermes import Agent, Session

agent = Agent.from_config("hermes.toml")

# Start a run. If a session with this ID exists, it resumes automatically.
session = Session(id="nightly-ingest-2026-10-30", store=agent.store)

result = agent.run(
    task="Fetch yesterday's S3 access logs, flag any IP with >1000 403s, "
         "and write a summary to /reports/access-$(date +%F).md",
    session=session,
)

print(f"status={result.status} steps={result.steps} cost=${result.cost_usd:.2f}")

If the process dies mid-run, you re-execute the same code and Hermes picks up from the last checkpoint. The result.status will be "resumed" on that second invocation, which is useful for alerting — you can page on repeated resumes rather than treating them as normal.

Real-world application: A team running nightly compliance scans across 200,000 log files had runs that occasionally exceeded the container’s 2-hour limit. Before Hermes, a timeout meant starting over. With checkpointing, the resumed run picks up at file ~187,000 and finishes in minutes. The cost saving is real, but the operational win — no more 2 a.m. pages about a scan that never completed — is bigger.

Feature 2: Typed Tool Definitions with Provider Fallback

Tools are defined with Pydantic schemas. Hermes generates the provider-specific tool-calling format, and for models without native function calling it uses a constrained-decoding shim.

from pydantic import BaseModel, Field
from hermes import tool

class QueryParams(BaseModel):
    sql: str = Field(description="A read-only SELECT statement")
    limit: int = Field(default=100, le=5000)

@tool(name="run_sql", description="Execute a read-only SQL query against the analytics warehouse.")
def run_sql(params: QueryParams) -> list[dict]:
    # Hermes enforces read-only at the connection level; this is defense in depth.
    if not params.sql.strip().lower().startswith("select"):
        raise ValueError("Only SELECT statements are permitted")
    import psycopg
    with psycopg.connect("postgresql://readonly@warehouse/analytics") as conn:
        with conn.cursor() as cur:
            cur.execute(params.sql)
            return [dict(zip([d.name for d in cur.description], row))
                    for row in cur.fetchmany(params.limit)]

agent = Agent.from_config("hermes.toml", tools=[run_sql])

The Field descriptions aren’t decoration — Hermes passes them to the model verbatim, and in our testing, tightening a single field description from “the SQL” to “a read-only SELECT statement” cut malformed tool calls by roughly a third. Treat the schema as part of your prompt.

Real-world application: An internal analytics bot that serves both Claude and a self-hosted Qwen model for cost reasons. The tool definitions are identical; only the [providers] block in the config changes. When the team wants to A/B a new model, they flip one line and re-run their eval suite.

Feature 3: Sub-Agent Isolation

A parent agent can delegate to a child agent that runs with its own context window and returns only a summary. This is the most important cost-control mechanism in Hermes.

from hermes import Agent, SubAgent

researcher = SubAgent(
    name="researcher",
    model="claude-haiku-4-5",     # cheaper model for the grunt work
    tools=["web_search", "fetch_url"],
    max_steps=25,
    return_format="summary",       # only a summary enters the parent's context
)

orchestrator = Agent.from_config("hermes.toml", subagents=[researcher])

result = orchestrator.run(
    task="Research the three largest EU AI Act enforcement actions in 2026 "
         "and produce a one-page brief with citations."
)

Without sub-agent isolation, that task would pull dozens of full web pages into the orchestrator’s context. With it, the orchestrator sees a ~400-token summary per research thread. In a benchmark we ran on a 12-source research task, the parent context stayed under 9,000 tokens versus ~140,000 without isolation — roughly a 15x reduction, which maps almost linearly to cost.

Real-world application: A due-diligence workflow that researches 30 companies in parallel. Each company gets its own sub-agent; the orchestrator assembles the final report. Wall-clock time dropped from 22 minutes (sequential) to about 4 minutes (parallel sub-agents, capped at 8 concurrent), and the orchestrator never blew its context window.


🛠️ Advanced Workflows

Workflow 1: Crash-Safe Data Pipeline with Human-in-the-Loop Approval

This pattern is for workflows where some steps are expensive or irreversible and you want a human gate before they execute.

# 1. Run the agent; it pauses at the approval gate and exits cleanly
hermes run --config hermes.toml --task-file tasks/migrate_customers.md \
  --session migrate-2026-10-30 --pause-on approval

# Output:
# [hermes] step 12/40: proposed 3 destructive operations
# [hermes] paused for approval. session=migrate-2026-10-30
# [hermes] review: hermes inspect migrate-2026-10-30 --pending

# 2. Inspect what it wants to do
hermes inspect migrate-2026-10-30 --pending
# PENDING APPROVAL:
#   DROP INDEX idx_customers_legacy ON customers
#   UPDATE customers SET tier = 'gold' WHERE lifetime_value > 50000  (est. 1,204 rows)
#   ALTER TABLE customers DROP COLUMN legacy_notes

# 3. Approve or reject specific operations
hermes approve migrate-2026-10-30 --all --except "DROP COLUMN legacy_notes"

# 4. Resume; the agent continues with the approved subset
hermes resume migrate-2026-10-30

The key detail: the paused session is fully persisted, so approval can happen hours later, from a different machine, by a different operator. The agent doesn’t need to stay resident. This is the difference between “human-in-the-loop” as a feature and human-in-the-loop as something you can actually operate.

Workflow 2: Parallel Sub-Agent Fan-Out with Budget Enforcement

For batch workloads where each item is independent, fan out to sub-agents with a shared budget ceiling.

from hermes import Agent, SubAgent, BudgetExceeded

reviewer = SubAgent(
    name="reviewer",
    model="claude-haiku-4-5",
    tools=["read_file", "post_comment"],
    max_steps=15,
    return_format="summary",
)

orchestrator = Agent.from_config(
    "hermes.toml",
    subagents=[reviewer],
    max_concurrency=8,
    max_cost_usd=15.00,   # shared ceiling across all children
)

try:
    result = orchestrator.run(
        task="Review every PR opened in the last 24h in org/repo. "
             "Post a summary comment on each. Skip drafts.",
        fan_out="pull_requests",   # custom fan-out key resolved by a tool
    )
    print(f"reviewed={result.items_completed} cost=${result.cost_usd:.2f}")
except BudgetExceeded as e:
    # Partial results are still valid and checkpointed.
    print(f"stopped at ${e.spent:.2f}; {e.items_completed} PRs reviewed")

BudgetExceeded is not a failure mode you should treat as an error. The completed work is checkpointed and the partial results are usable. Design your workflows so a budget stop leaves things in a consistent state — in this case, every posted comment is already live, and the un-reviewed PRs simply get picked up on the next run.

One caveat from experience: max_concurrency=8 is a good default for API-rate-limited providers, but if you’re hitting a self-hosted model on a single GPU, drop it to 2-3. Over-subscribing a local inference server causes queueing that makes the whole run slower than sequential execution.


📊 Comparison with Alternatives

FeatureHermes 2.4LangGraphAutoGen
Durable checkpoints (default)✅✅ (opt-in)❌
Resume across process restarts✅✅⚠️ partial
Provider-agnostic tool calling✅⚠️ via adapters⚠️ via adapters
Sub-agent context isolation✅ built-in⚠️ manual✅
Hard per-run cost ceiling✅❌❌
Human-in-the-loop pause/resume✅✅⚠️ limited
Local model support (Ollama/vLLM)✅ first-class✅✅
Learning curve (subjective)LowHighMedium
Config-as-code (TOML)✅❌ (Python only)❌ (Python only)
Rust-accelerated store✅ optional❌❌

A fair reading: LangGraph is more flexible for genuinely complex graph topologies, and if your workflow looks like a state machine with many conditional branches, it may be the better fit. AutoGen’s conversational multi-agent model is still the most natural for open-ended dialogue between agents. Hermes wins on operational durability and cost control, which is where most production teams actually get stuck.


🎯 Pro Tips

1. Checkpoint granularity should match your most expensive step, not your step count. If your agent occasionally makes a 90-second API call, set checkpoint_every = 1 for that workflow. The write cost is trivial compared to re-executing the call. If all steps are fast, checkpoint_every = 5 is fine.

2. Use sub-agents for anything you’d otherwise summarize manually. A good heuristic: if you find yourself writing a prompt that says “read these 20 documents and tell me the key points,” that’s a sub-agent. The parent should never see the 20 documents.

3. Pin your model versions in config, not in code. model = "claude-sonnet-4-5" in hermes.toml means you can A/B a model swap without touching a line of Python, and your eval suite can run against both versions by swapping one file. This sounds minor until the first time a provider silently updates a model and your workflow’s behavior shifts.

4. Treat BudgetExceeded as a first-class outcome. Wrap fan-out workflows in a try/except and ensure partial completion is a valid end state. Workflows that only succeed or fail completely are fragile at scale.

5. Run hermes doctor in CI. It catches credential drift and store connectivity issues in seconds, before your nightly job fails at 3 a.m. We run it as a pre-flight step in every scheduled workflow.


🔗 Resources


Hermes isn’t the most flexible agent framework, and it isn’t trying to be. It’s the one that assumes your workflow will fail halfway through and plans for it. If you’re moving agents from notebooks to scheduled jobs, that assumption is worth more than any amount of prompt cleverness.


Have questions? Join our Discord community or follow us on X.