Skip to main content

Async Hermes Agent

Native-async, library-focused distribution of NousResearch/hermes-agent, based on upstream tag v2026.8.13 (Python package version 0.20.1).

This repository keeps the Hermes agent loop, model providers, tool execution, MCP, skills, persistent memory and sessions, trajectory generation, runner, and batch runner. The CLI/TUI, messaging bridges, scheduler, dashboard, and FastAPI application are intentionally outside this package.

The public core API keeps the upstream names and module locations. Existing library integrations normally only need to add await:

import asyncio
import os

from run_agent import AIAgent


async def main():
    async with AIAgent(
        provider="openrouter",
        model="openrouter/auto",
        api_key=os.environ["OPENROUTER_API_KEY"],
    ) as agent:
        result = await agent.run_conversation("Investigate this repository")
        print(result["final_response"])


asyncio.run(main())

Inside an async function, the compact string-returning interface and explicit lifecycle are:

async def chat_once():
    agent = AIAgent(provider="openrouter", model="openrouter/auto")
    try:
        return await agent.chat("Summarize the result")
    finally:
        await agent.close()

AIAgent.__init__() performs state-only construction. Configuration, provider clients, session storage, and MCP connections initialize lazily at the first awaited boundary. Turns on one AIAgent instance are serialized; separate instances can run concurrently.

Install

Python 3.11 through 3.13 is supported.

uv pip install "async-hermes-agent==0.20.1.2"

Versioned packages are published to PyPI through GitHub OIDC Trusted Publishing. The same verified wheel, source distribution, and checksums are attached to the corresponding GitHub Release.

The package version has four numeric segments: 0.20.1.2 means upstream Python version 0.20.1 plus async-distribution revision 2. Fork-only releases increment the fourth segment. When a new upstream version is ported, the first three segments change to match it and the async revision restarts at 1.

The earlier 0.20.4 GitHub release used the old independent version scheme. If it was installed from that Git tag, migrate explicitly once:

uv pip install --reinstall "async-hermes-agent==0.20.1.2"

For development:

git clone https://github.com/ykoh42/async-hermes-agent.git
cd async-hermes-agent
uv sync --extra dev

Provider-specific dependencies remain opt-in, for example:

uv sync --extra anthropic
uv sync --extra vertex
uv sync --extra azure-identity
uv sync --extra supermemory
uv sync --extra hindsight
uv sync --extra honcho

The installation guide lists every current extra, including retained media, execution-backend, and memory providers.

The Hindsight extra covers cloud and local-external modes. Its local_embedded mode additionally requires the upstream hindsight-all runtime.

The Honcho extra pins the native-async SDK version validated by this package. Select memory.provider: honcho in config.yaml; connection, identity, cadence, and session settings are documented in the Honcho provider guide.

OpenViking uses the core native-async HTTP transport and needs no Python extra. Server setup, provider configuration, async lifecycle, recall, and tool behavior are documented in the OpenViking provider guide.

Sessions

SessionDB keeps the upstream export and import names under the original hermes_state.py path. SQLite reads, writes, lineage reconstruction, and resource cleanup are awaited directly:

import asyncio

from hermes_state import SessionDB


async def copy_sessions():
    source = SessionDB("state.db")
    restored = SessionDB("restored-state.db")
    try:
        exported = await source.export_all()
        return await restored.import_sessions(exported)
    finally:
        await source.close()
        await restored.close()


asyncio.run(copy_sessions())

export_all(), export_session(), and import_sessions() preserve the upstream dictionaries and validation limits. Import restores conversation history but deliberately clears stale live-activity fields.

For an explicit PostgreSQL backend, install the opt-in extra and inject one worker-owned store into each agent. CI exercises this backend against real PostgreSQL services; the local test suite skips those integration tests when no HERMES_POSTGRES_TEST_DSN is configured. The import and method names stay the same; only the backend module and DSN change:

uv sync --extra postgres
from hermes_state_postgres import SessionDB
from run_agent import AIAgent

db = SessionDB("postgresql+asyncpg://user:password@db.example/hermes")
try:
    async with AIAgent(provider="openrouter", session_db=db) as agent:
        answer = await agent.run_conversation("Question")
finally:
    await db.close()

The DSN selects the PostgreSQL endpoint and credentials. Pool and asyncpg runtime settings are optional and use the same names as SQLAlchemy and asyncpg in the active profile's config.yaml:

database:
  postgres:
    pool_size: 5
    max_overflow: 10
    pool_timeout: 30
    pool_recycle: -1
    pool_pre_ping: true
    pool_use_lifo: false
    connect_args:
      timeout: 60
      command_timeout: null
      statement_cache_size: 100
      max_cached_statement_lifetime: 300
      max_cacheable_statement_size: 15360
      server_settings:
        application_name: async-hermes-agent
        statement_timeout: "60000"
        lock_timeout: "5000"
        idle_in_transaction_session_timeout: "600000"

These settings are captured when the store is first initialized and are not hot-reloaded; create a new store after changing them. A read-only store keeps the same public constructor and enforces both the SessionDB write guard and PostgreSQL transaction-level read-only mode:

readonly_db = SessionDB(
    "postgresql+asyncpg://user:password@db.example/hermes",
    read_only=True,
)

read_only=True does not choose a replica automatically; the DSN still selects the endpoint. For a service, size the database for the possible connection count across workers: approximately workers * (pool_size + max_overflow).

The injected store is borrowed by the agent, so a FastAPI or ASGI lifespan should create and close one store per worker and share it among that worker's agents. SQLite remains the default. PostgreSQL uses native ranking rather than SQLite FTS5 BM25 scores, and the independent memory/delegation databases are still SQLite-backed.

Skills, MCP, and memory

Skills follow the existing Hermes layout. HERMES_HOME defaults to ~/.hermes; put each active skill at:

$HERMES_HOME/skills/<skill-name>/SKILL.md

Each SKILL.md is a normal Hermes skill document with YAML frontmatter:

---
name: code-review
description: Review a code change before it is merged.
---

# Code review

Read the change, run its tests, and report correctness issues first.

Upstream Hermes seeds its source-bundled skills through the product installer. This library does not include that installer, so Git/wheel users add skill directories explicitly or point at shared directories in config.yaml:

skills:
  external_dirs:
    - ~/.agents/skills
    - /shared/team-skills

The skills_list and skill_view tools discover both the local and configured external directories. Skill content remains outside the model-tool schema until the model selects and reads it.

MCP servers are configured under mcp_servers in $HERMES_HOME/config.yaml:

mcp_servers:
  filesystem:
    command: npx
    args: ["-y", "@modelcontextprotocol/server-filesystem", "/workspace"]

The first awaited agent boundary discovers configured servers and registers their tools under the server's toolset. MCP subprocesses and client sessions are closed by await agent.close() or the async context manager.

The file-backed memory and user profile surfaces also retain the normal Hermes home under ~/.hermes. Enable the memory toolset and the corresponding memory settings in config.yaml when constructing a memory-enabled agent.

Training and trajectories

Set save_trajectories=True on AIAgent for individual conversations. The saved sequence preserves reasoning, tool calls, observations, and the final answer for interleaved-thinking fine-tuning. Completed samples append to trajectory_samples.jsonl in the process working directory.

The upstream single-task training runner is retained at the same mini_swe_runner.py import path. Its provider, terminal execution, cleanup, and JSONL batch methods are native coroutines; the trajectory conversion and return shapes remain unchanged:

import asyncio

from mini_swe_runner import MiniSWERunner


async def run_one_task():
    runner = MiniSWERunner(
        model="openai/gpt-oss-20b:free",
        env_type="local",
        cwd="/workspace",
    )
    return await runner.run_task("Inspect and repair the project")


result = asyncio.run(run_one_task())

For datasets, use BatchRunner from the unchanged batch_runner.py module and await its existing run() method. It retains bounded concurrency, checkpoints, resume support, and JSONL output. trajectory_compressor.py remains available for post-processing generated trajectories.

import asyncio
import os

from batch_runner import BatchRunner


async def main():
    runner = BatchRunner(
        dataset_file="prompts.jsonl",
        batch_size=8,
        run_name="tool-training",
        distribution="terminal_only",
        base_url="https://openrouter.ai/api/v1",
        api_key=os.environ["OPENROUTER_API_KEY"],
        model="openai/gpt-oss-20b:free",
        num_workers=4,
        reasoning_config={"enabled": True, "effort": "low"},
    )
    await runner.run(resume=True)


asyncio.run(main())

Each input line must be JSON with a prompt field. Outputs are written under data/<run_name>/: per-batch JSONL shards, merged trajectories.jsonl, checkpoint.json, and statistics.json.

Service integration

No web framework is bundled. A service should own its HTTP lifecycle and await the library directly:

from fastapi import FastAPI
from run_agent import AIAgent

app = FastAPI()

@app.post("/chat")
async def chat(message: str):
    # One AIAgent is one mutable conversation. A real host should keep one
    # instance per conversation ID; this short-lived example isolates calls.
    async with AIAgent(provider="openrouter", model="openrouter/auto") as agent:
        return await agent.run_conversation(message)

Provider, network, MCP, and subprocess paths use coroutine transports, and optional providers without one fail explicitly. The filesystem layer uses aiofiles, whose regular-file operations delegate to an executor, while aiosqlite serializes SQLite calls on a connection worker thread. Eliminating those portable Python limitations is outside the package's native-async contract: public I/O remains directly awaitable and does not block the host event loop, but the project does not claim zero-thread, OS-native regular-file or embedded-SQLite I/O.

Verification

uv run pytest -q
uv run ruff check agent tools hermes_cli plugins providers \
  run_agent.py model_tools.py mini_swe_runner.py batch_runner.py hermes_state.py \
  hermes_state_portability.py \
  hermes_state_schema.py \
  trajectory_compressor.py
uv build

Contributing and security

Read CONTRIBUTING.md before submitting changes and SECURITY.md for private vulnerability reporting.

Upstream relationship

The repository preserves original core file and function names to keep future upstream imports reviewable. It is a divergent async distribution, not a claim that these changes are drop-in mergeable to the synchronous upstream product.

The deliberate differences from upstream v2026.8.13 are documented in the upstream differences table.

Hermes Agent is built by Nous Research. This distribution retains the upstream MIT license; see LICENSE.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

async_hermes_agent-0.20.1.2.tar.gz (3.7 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

async_hermes_agent-0.20.1.2-py3-none-any.whl (3.8 MB view details)

Uploaded Python 3

File details

Details for the file async_hermes_agent-0.20.1.2.tar.gz.

File metadata

  • Download URL: async_hermes_agent-0.20.1.2.tar.gz
  • Upload date:
  • Size: 3.7 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for async_hermes_agent-0.20.1.2.tar.gz
Algorithm Hash digest
SHA256 82deaa54f4fb9a31a1935bbfaeb3d219ce3611a86b8c854c3322e008341d1e30
MD5 4d904967a3fc61dae6e5ae09de9624c6
BLAKE2b-256 c612b7c3b01891f02a0e7d614f4c9ee5c9bf39e908ffde304dbc1d09092fcd18

See more details on using hashes here.

Provenance

The following attestation bundles were made for async_hermes_agent-0.20.1.2.tar.gz:

Publisher: release.yml on ykoh42/async-hermes-agent

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file async_hermes_agent-0.20.1.2-py3-none-any.whl.

File metadata

File hashes

Hashes for async_hermes_agent-0.20.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 a8326fa9eea5d6ba91f492d38c6df8fbdb394d8398a437018d1b6b83c79f7675
MD5 8db05dec8f6c2fd1df3b74de4e03e12d
BLAKE2b-256 0f4eefbd3b8a93b18801dec3d895274d3af58d0fc086a0c55ce1c15c492d9ed4

See more details on using hashes here.

Provenance

The following attestation bundles were made for async_hermes_agent-0.20.1.2-py3-none-any.whl:

Publisher: release.yml on ykoh42/async-hermes-agent

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page