Skip to main content

Async Hermes Agent

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

This repository keeps the Hermes agent loop, model providers, tool execution, MCP, skills, persistent memory and sessions, trajectory generation, runner, and batch runner. The interactive Hermes CLI/TUI, messaging bridges, scheduler, dashboard, and FastAPI application are intentionally outside this package. The upstream-compatible batch_runner.py script entrypoint remains available for dataset generation.

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.2.1"

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.2.1 means upstream Python version 0.20.2 plus async-distribution revision 1. 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.2.1"

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+psycopg://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 psycopg runtime settings are optional and use the same names as SQLAlchemy and psycopg 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:
      connect_timeout: 60
      prepare_threshold: 5
      application_name: async-hermes-agent
      options: >-
        -c statement_timeout=60000
        -c lock_timeout=5000
        -c 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+psycopg://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 PostgreSQL settings use psycopg/libpq names. connect_timeout controls connection establishment, prepare_threshold: null disables prepared statements, and server timeouts are passed through libpq's options string. The old asyncpg-only names (timeout, command_timeout, server_settings, and asyncpg statement-cache options) are not accepted.

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. When an explicit PostgreSQL store is injected into AIAgent(session_db=...), durable async-delegation records and session_search(db=...) use that same store; the default SQLite delegation path and independent memory/plugin databases remain unchanged.

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, the upstream-compatible command-line entrypoint remains available from a source checkout:

python batch_runner.py \
    --dataset_file=data/prompts.jsonl \
    --batch_size=10 \
    --run_name=my_first_run \
    --model=anthropic/claude-sonnet-4.6 \
    --num_workers=4

# Resume an interrupted run.
python batch_runner.py \
    --dataset_file=data/prompts.jsonl \
    --batch_size=10 \
    --run_name=my_first_run \
    --resume

An installed package also supports python -m batch_runner .... The CLI uses Fire only at that process boundary; an async web host does not use Fire or create a nested event loop. For library and FastAPI use, import BatchRunner and await its existing run() method directly. 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.16 are documented in the upstream differences table.

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

Metadata

Release files for async-hermes-agent 0.20.2.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for async-hermes-agent 0.20.2.1
File Size Uploaded
async_hermes_agent-0.20.2.1.tar.gz 3.8 MB Details

Built distribution (wheel)

Table of built distributions (wheels) for async-hermes-agent 0.20.2.1
File Interpreter ABI Platform
async_hermes_agent-0.20.2.1-py3-none-any.whl Python 3 none any Details

Total release size: 7.7 MB

Release files / async_hermes_agent-0.20.2.1.tar.gz

Download URL async_hermes_agent-0.20.2.1.tar.gz
Size 3.8 MB
Tags Source
SHA-256 checksum
How to use checksums
d9425cb6f98abcc1346e87dbde91b85096ca747012d3355b5ac604c17e0cbe9d
BLAKE2b-256 checksum
How to use checksums
0516797faa0428d545497d9ceb73cf89f0570ac1f1c256cfd8e775a7d80641ee
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.13

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 17, 2026.

Transparency log

Release files / async_hermes_agent-0.20.2.1-py3-none-any.whl

Download URL async_hermes_agent-0.20.2.1-py3-none-any.whl
Size 3.9 MB
Tags Python 3
SHA-256 checksum
How to use checksums
17b59d503ba4a3a833b7de5338295aaf16ac22d427cc0bc38cc1d5944e3727f6
BLAKE2b-256 checksum
How to use checksums
4350c4a9a2fb969492cc11e1e491b2aaef64807e3f5a6dc4d2725f448a3b0dfe
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.13

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 17, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.20.2.1 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page