Async Hermes Agent
Native-async, library-focused distribution of
NousResearch/hermes-agent, based
on upstream tag v2026.8.3 (Python package version 0.20.0).
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.0.5"
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.0.5 means upstream
Python version 0.20.0 plus async-distribution revision 5. 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 and
sorts after 0.20.0.5 under Python version ordering. If it was installed from
that Git tag, migrate explicitly once:
uv pip install --reinstall "async-hermes-agent==0.20.0.5"
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.
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.
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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file async_hermes_agent-0.20.0.5.tar.gz.
File metadata
- Download URL: async_hermes_agent-0.20.0.5.tar.gz
- Upload date:
- Size: 3.6 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1e426fd6769d0d1a88d75f0f694d61ad1434e23a6edf9baf8fb8c4b9d8777083
|
|
| MD5 |
dc6f95cb888bbce27d4cefb55f805f58
|
|
| BLAKE2b-256 |
6291575fa01d37076d52aeb8f2bd851b8a897dc546fa23eb1fca51555376ae6a
|
Provenance
The following attestation bundles were made for async_hermes_agent-0.20.0.5.tar.gz:
Publisher:
release.yml on ykoh42/async-hermes-agent
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
async_hermes_agent-0.20.0.5.tar.gz -
Subject digest:
1e426fd6769d0d1a88d75f0f694d61ad1434e23a6edf9baf8fb8c4b9d8777083 - Sigstore transparency entry: 2452660817
- Sigstore integration time:
-
Permalink:
ykoh42/async-hermes-agent@98b030905bf43fdfa1e58efa87c6548ff8bb5709 -
Branch / Tag:
refs/tags/v0.20.0.5 - Owner: https://github.com/ykoh42
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@98b030905bf43fdfa1e58efa87c6548ff8bb5709 -
Trigger Event:
push
-
Statement type:
File details
Details for the file async_hermes_agent-0.20.0.5-py3-none-any.whl.
File metadata
- Download URL: async_hermes_agent-0.20.0.5-py3-none-any.whl
- Upload date:
- Size: 3.7 MB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
192e0b82eea4735b97525507451e9162986f9defeae9716c81471521e49869e2
|
|
| MD5 |
cc20e68be39e544106e958b703183c9d
|
|
| BLAKE2b-256 |
d4cdc43b60998929feb71d81912d62cb358e8a78f42233274cff387513c46fb0
|
Provenance
The following attestation bundles were made for async_hermes_agent-0.20.0.5-py3-none-any.whl:
Publisher:
release.yml on ykoh42/async-hermes-agent
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
async_hermes_agent-0.20.0.5-py3-none-any.whl -
Subject digest:
192e0b82eea4735b97525507451e9162986f9defeae9716c81471521e49869e2 - Sigstore transparency entry: 2452660911
- Sigstore integration time:
-
Permalink:
ykoh42/async-hermes-agent@98b030905bf43fdfa1e58efa87c6548ff8bb5709 -
Branch / Tag:
refs/tags/v0.20.0.5 - Owner: https://github.com/ykoh42
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@98b030905bf43fdfa1e58efa87c6548ff8bb5709 -
Trigger Event:
push
-
Statement type: