Hermes Agent

Data Model — v0.14.0 — Nous Research

The data structures at the heart of Hermes Agent — the database schema that persists sessions and messages, the message format that flows between the agent and LLM providers, the tool registry that powers 40+ capabilities, the skill file format, the layered configuration system, and the pluggable memory provider interface.

4
Storage layers
40+
Config keys
40+
Registered tools
56
Toolset groups
11
Schema versions
11
Auxiliary tasks
Domain Entities

Core Entity Map

Where each entity lives. Sessions and messages persist in SQLite, tools and config rebuild from code and YAML at startup, skills and plugins live on the filesystem, and credentials rotate through a managed JSON store.

Entity Storage Map · Ctrl/Cmd + wheel to zoom · Drag to pan · Double-click to fit

Loading...
SQLite (state.db)
SQLite (kanban.db)
Filesystem (~/.hermes/)
Runtime (rebuilt at startup)
Persistence

SQLite Schema

All state lives in ~/.hermes/state.db — a SQLite database with WAL mode for concurrent reads. Schema version 11. FTS5 for full-text search across message content, tool names, and tool-call arguments, with a trigram index for CJK/substring queries.

Write contention: Uses BEGIN IMMEDIATE transactions with jitter retry (15 retries, 20–150ms random sleep). Passive WAL checkpoint every 50 writes. PRAGMA journal_mode=WAL; PRAGMA foreign_keys=ON;

sessions v1 + v3 + v4 + v5

ColumnTypeNotes
idTEXTPK UUID
sourceTEXT NOT NULLIDX "cli", "telegram", "discord", etc.
user_idTEXTPlatform user identifier
modelTEXTLLM model slug
model_configTEXTJSON-encoded dict
system_promptTEXTFrozen at session start for cache
parent_session_idTEXTFK IDX Compression chain link
started_atREAL NOT NULLIDX Epoch timestamp, DESC
ended_atREALNULL while active
end_reasonTEXT"user", "max_iterations", etc.
message_countINTEGERDefault 0
tool_call_countINTEGERDefault 0
input_tokensINTEGERDefault 0
output_tokensINTEGERDefault 0
cache_read_tokensINTEGERv5 Anthropic cache hits
cache_write_tokensINTEGERv5 Anthropic cache writes
reasoning_tokensINTEGERv5 Extended thinking tokens
billing_providerTEXTv5
billing_base_urlTEXTv5
billing_modeTEXTv5
estimated_cost_usdREALv5
actual_cost_usdREALv5
cost_statusTEXTv5
cost_sourceTEXTv5
pricing_versionTEXTv5
titleTEXTv3 UNIQUE partial (WHERE NOT NULL)
api_call_countINTEGERDefault 0, tool-calling API rounds

messages v1 + v2 + v6

ColumnTypeNotes
idINTEGERPK AUTOINCREMENT
session_idTEXT NOT NULLFK IDX Composite with timestamp
roleTEXT NOT NULLsystem | user | assistant | tool
contentTEXTMessage text (also indexed in FTS5)
tool_call_idTEXTLinks tool result to its call
tool_callsTEXTJSON-encoded array of tool calls
tool_nameTEXTWhich tool produced this result
timestampREAL NOT NULLEpoch timestamp
token_countINTEGEREstimated tokens
finish_reasonTEXTv2 stop | length | tool_calls
reasoningTEXTv6 Extended thinking content
reasoning_contentTEXTProvider-specific reasoning text
reasoning_detailsTEXTv6 JSON-encoded details
codex_reasoning_itemsTEXTv6 JSON-encoded Codex items
codex_message_itemsTEXTJSON-encoded Codex message items

messages_fts FTS5 Virtual Table

Full-text search index over content || tool_name || tool_calls. Inline-mode FTS5 (since v11); triggers keep the index synced with the messages table.

CREATE VIRTUAL TABLE messages_fts USING fts5(content);

-- Auto-sync triggers (v11 — indexes content + tool_name + tool_calls)
AFTER INSERT ON messages →
    INSERT INTO messages_fts(rowid, content) VALUES (
        new.id,
        COALESCE(new.content, '') || ' ' ||
        COALESCE(new.tool_name, '') || ' ' ||
        COALESCE(new.tool_calls, '')
    );

AFTER DELETE ON messages →
    DELETE FROM messages_fts WHERE rowid = old.id;

AFTER UPDATE ON messages →
    DELETE old entry, INSERT new entry (same concat as INSERT).

Database ER Diagram · Ctrl/Cmd + wheel to zoom · Drag to pan · Double-click to fit

Loading...

Migration History

VersionChange
v1Initial schema: sessions, messages, messages_fts, schema_version
v2Added finish_reason to messages
v3Added title to sessions
v4Added unique partial index on sessions.title (WHERE NOT NULL)
v5Added 11 billing/cost/cache columns to sessions
v6Added reasoning, reasoning_details, codex_reasoning_items to messages
v7–v9Declarative column reconciliation — new columns added via schema diff, no explicit migrations
v10Added messages_fts_trigram (trigram FTS5 for CJK/substring search) with row backfill
v11FTS5 re-indexed to cover content || tool_name || tool_calls (inline-mode triggers replace external-content); enables session_search to find tool names and tool-call arguments, not just message content
Wire Format

Message Format

Messages follow the OpenAI chat completions format. This is the lingua franca — regardless of whether the backend is Anthropic, OpenAI, or a custom endpoint, messages are normalized to this shape.

User Message

{
  "role": "user",
  "content": "Search for recent papers on GRPO"
}

Assistant + Tool Calls

{
  "role": "assistant",
  "content": null,
  "tool_calls": [
    {
      "id": "call_abc123",
      "type": "function",
      "function": {
        "name": "web_search",
        "arguments": "{\"query\": \"GRPO ...\"}"
      }
    }
  ]
}

Tool Result

{
  "role": "tool",
  "tool_call_id": "call_abc123",
  "tool_name": "web_search",
  "content": "{\"data\": {\"web\": [...]}}"
}

System Message

{
  "role": "system",
  "content": "You are a CLI AI Agent..."
}
// Frozen at session start.
// Never modified mid-conversation
// (preserves Anthropic prompt cache).

Message Flow Through the Agent Loop · Ctrl/Cmd + wheel to zoom · Drag to pan · Double-click to fit

Loading...
Reasoning content: When using extended thinking (Anthropic, Codex), the reasoning field stores chain-of-thought. It is persisted to SQLite but not sent back to the LLM in subsequent turns — it's for display and debugging only.
Tool System

Tool Registry & Toolsets

Tools self-register at import time into a singleton registry. Toolsets group related tools and compose into platform-level bundles. See C4 Architecture § Tools for the high-level view.

Tool Registry Class Diagram · Ctrl/Cmd + wheel to zoom · Drag to pan · Double-click to fit

Loading...

OpenAI Function Schema Format

Every tool registers a schema dict following the OpenAI function calling format. When served to the LLM via get_definitions(), each is wrapped as {"type": "function", "function": {schema}}.

{
  "name": "read_file",
  "description": "Read a text file with line numbers...",
  "parameters": {
    "type": "object",
    "properties": {
      "path": {
        "type": "string",
        "description": "Path to the file to read"
      },
      "offset": {
        "type": "integer",
        "default": 1,
        "minimum": 1
      },
      "limit": {
        "type": "integer",
        "default": 500,
        "maximum": 2000
      }
    },
    "required": ["path"]
  }
}

Toolset Composition

Toolsets are either leaf (directly list tools) or composite (include other toolsets). Platform toolsets compose everything needed for a deployment mode.

Leaf Toolsets
web · search · x_search · vision · video · image_gen · video_gen · computer_use · terminal · moa · skills · browser · cronjob · messaging · file · tts · todo · memory · session_search · clarify · code_execution · delegation · homeassistant · kanban · discord · discord_admin · yuanbao · feishu_doc · feishu_drive · spotify
Composite Toolsets
debugging = terminal + process + web + file
safe = moa + web + vision + image_gen
Platform Toolsets
hermes-cli · hermes-cron · hermes-gateway · hermes-acp · hermes-api-server · hermes-webhook · hermes-telegram · hermes-discord · hermes-slack · hermes-whatsapp · hermes-signal · hermes-bluebubbles · hermes-matrix · hermes-mattermost · hermes-email · hermes-sms · hermes-homeassistant · hermes-dingtalk · hermes-feishu · hermes-weixin · hermes-qqbot · hermes-wecom · hermes-wecom-callback · hermes-yuanbao
Two-tier dispatch: Agent-level tools (todo, memory, session_search, clarify, delegate_task) are handled directly in _invoke_tool() inside run_agent.py — they never hit the registry. Everything else goes through handle_function_call()registry.dispatch(). See Sequences § Tool Execution.
Skills

Skill File Structure

Skills are Hermes's procedural memory — reusable markdown instructions with optional supporting files. They are injected as user messages (not system prompt) to preserve prompt cache efficiency.

Directory Layout

  • skills/
    • <category>/
      • <skill-name>/
        • SKILL.md — Required. YAML frontmatter + markdown body
        • scripts/ — Helper scripts (Python, Bash, etc.)
        • references/ — Reference documents
        • templates/ — File templates
        • assets/ — Static assets

Categories: creative, devops, github, mlops, productivity, research, software-development, social-media, etc.

YAML Frontmatter Schema

---
name: visual-explainer                    # Required: kebab-case slug
description: Generate rich HTML docs...   # Required: when to use
version: 1.0.0                           # Required: semver
author: Hermes Agent                     # Required
license: MIT                             # Required
dependencies: []                         # Optional: pip/system deps
prerequisites:                           # Optional
  env_vars: [LINEAR_API_KEY]             #   Required env vars
  commands: [curl]                       #   Required CLI tools
metadata:
  hermes:
    tags: [architecture, diagrams]       # Searchable tags
    related_skills: [excalidraw]         # Cross-references
---

# Skill Title

## When to Use
...

## Workflow
...

## Pitfalls
...

Skill Loading and Injection · Ctrl/Cmd + wheel to zoom · Drag to pan · Double-click to fit

Loading...
Configuration

Config Hierarchy

Configuration resolves through four layers. Lower layers provide defaults; higher layers override. ${VAR} references in YAML values are expanded from environment variables.

① Runtime Overrides
CLI flags, /model command, API parameters. Highest priority. Transient.
② Environment Variables
${VAR} expansion in config values. Also AUXILIARY_*_MODEL, AUXILIARY_*_PROVIDER for per-task overrides.
③ User Config File
~/.hermes/config.yaml — deep-merged over defaults. Persisted by save_config_value().
④ DEFAULT_CONFIG
Hardcoded in hermes_cli/config.py. 40+ top-level keys. Lowest priority. Always present.

Top-Level Config Keys

Core
model
providers
fallback_providers
toolsets
agent
delegation
Execution
terminal
browser
compression
auxiliary
Interface
display
tts
stt
voice
human_delay
State
memory
skills
checkpoints
privacy
security
Platform
discord
whatsapp
approvals
cron
gateway

Auxiliary Task Configuration

Side tasks can each be routed to a different LLM provider. Every task has the same schema: provider, model, base_url, api_key, timeout, extra_body.

TaskDefault TimeoutPurpose
vision120sImage analysis (vision_analyze, browser_vision)
web_extract360s (6min)Web page summarization
compression120sContext compression summaries
skills_hub30sSkill search and matching
approval30sSmart approval risk assessment
mcp30sMCP server interactions
title_generation30sSession title generation
triage_specifier120sKanban triage → spec expansion
kanban_decomposer180sDecompose triage task into child-task graph
profile_describer60sAuto-generate profile descriptions
curator600sSkill-usage curator review pass

Config Resolution Flow · Ctrl/Cmd + wheel to zoom · Drag to pan · Double-click to fit

Loading...
Plugins

Plugin System

Hermes uses a plugin architecture for extensibility. Five plugin categories exist: memory providers, context engines, general-purpose plugins, dashboard plugins, and platform plugins. The PluginManager discovers and loads plugins from four sources in precedence order.

Plugin Architecture · Ctrl/Cmd + wheel to zoom · Drag to pan · Double-click to fit

Loading...

plugin.yaml Schema

FieldTypeDescription
namestrUnique plugin identifier
versionstrSemver version
descriptionstrHuman-readable description
authorstrPlugin author
requires_envlistRequired env vars (strings or dicts with details)
provides_toolslistTool names this plugin will register
provides_hookslistLifecycle hooks this plugin will register
kindstrPlugin category (standalone, backend, exclusive, platform)

Plugin Categories

CategoryConstraint
Memory ProviderAt most 1 active external provider
Context EngineAt most 1 replaces built-in compressor
Model ProviderInference backend (kind: model-provider); registers default/aux model + API mode; lives in plugins/model-providers/
Web Search ProviderSearch/extract backend (kind: backend, provides_web_providers); lives in plugins/web/<name>/ (brave_free, ddgs, exa, firecrawl, parallel, searxng, tavily, xai)
Browser ProviderCloud browser backend (kind: backend, provides_browser_providers); lives in plugins/browser/<name>/ (browserbase, browser_use, firecrawl)
General PluginOpt-in via config.yaml enable list
Dashboard PluginJS bundle + manifest.json for web UI tabs
Platform PluginRegisters adapter via PlatformRegistry; lives in plugins/platforms/

14 Lifecycle Hooks

Plugins register callbacks for any combination of these events via ctx.register_hook(event, callback).

pre_tool_call post_tool_call pre_llm_call post_llm_call pre_api_request post_api_request transform_terminal_output transform_tool_result on_session_start on_session_end on_session_finalize on_session_reset subagent_stop on_memory_write
Memory

Memory Provider Interface

Memory is pluggable. The built-in provider (file-based, always active) handles memory and user targets. At most one external provider (Honcho, etc.) can be added. The MemoryManager fans out calls to all active providers. See C4 Architecture § State for the provider list and runtime behaviour.

Memory Provider Class Hierarchy · Ctrl/Cmd + wheel to zoom · Drag to pan · Double-click to fit

Loading...

Lifecycle Hooks

HookWhen
on_turn_startBefore each LLM call
on_pre_compressBefore context compression drops messages
on_memory_writeWhen builtin memory is modified
on_delegationAfter a subagent completes
on_session_endSession teardown

initialize() kwargs

ParamType
session_idstr
hermes_homestr (auto-injected)
platformstr
user_idstr
agent_contextstr
agent_identitystr
agent_workspacestr
parent_session_idstr