Python Library
Async Hermes Agent is designed to be embedded. It ships the agent harness, not an application server or user interface.
Core interface
from run_agent import AIAgent
async def chat_once():
agent = AIAgent(...)
try:
result = await agent.run_conversation("Question")
answer = await agent.chat("Follow-up question")
return result, answer
finally:
await agent.close()
The method names and argument shapes remain at their upstream locations. The
methods are coroutines; calls without await only create coroutine objects.
Prefer the context manager:
async def complete_task():
async with AIAgent(...) as agent:
return await agent.run_conversation("Complete this task")
Entering initializes the selected provider, discovers plugins, and establishes
configured MCP lifecycles. Exiting closes model clients, MCP sessions, child
tasks, memory providers, and an attached session database. close() is
idempotent.
Conversation results
The stable result surface includes:
result["final_response"]
result["messages"]
result["completed"]
Normal completed turns can also include session_id, provider/model routing,
token and cost fields, API-call counts, reasoning, and turn-exit metadata. Early
terminal/error results and providers that do not report a usage field may omit
them, so consumers should use .get() for optional metadata and accounting
data.
chat() is a convenience interface that returns only final_response.
Conversation ownership
One agent represents one ordered conversation. Its turn lock serializes concurrent calls:
import asyncio
async def ordered_turns(agent):
# Safe, but these turns run in submission order rather than in parallel.
return await asyncio.gather(
agent.chat("First turn"),
agent.chat("Second turn"),
)
For independent work, allocate independent agents:
async def independent_turns():
async with AIAgent(...) as a, AIAgent(...) as b:
return await asyncio.gather(
a.chat("Independent task A"),
b.chat("Independent task B"),
)
Cancellation
Cancelling an active turn cancels its child work, persists a partial session
when a SessionDB is attached, and re-raises asyncio.CancelledError:
async def cancel_turn(agent):
task = asyncio.create_task(agent.run_conversation("Long task"))
task.cancel()
try:
await task
except asyncio.CancelledError:
pass
Hermes-internal interrupts are different: they return a partial result dict so the caller can display or inspect the work completed so far.
Explicit durable sessions
For a chosen database path and persistence beginning at the turn prologue,
construct and pass SessionDB explicitly:
from hermes_state import SessionDB
from run_agent import AIAgent
async def durable_turn():
db = SessionDB("./state.db")
async with AIAgent(..., session_db=db, session_id="customer-42") as agent:
return await agent.chat("Remember this conversation")
The database connection and schema initialize on the first awaited operation.
Ordinary turns without an injected store do not persist a transcript. A recall
tool that needs session storage can lazily open the default
$HERMES_HOME/state.db; the agent then owns and closes that handle. The agent
also owns and closes an explicitly supplied database at shutdown. See
Sessions for explicit resume handling.
Tools, skills, MCP, and memory
Select only the tool groups a conversation needs. Availability checks still remove a tool when its credential, executable, callback, or backend is absent:
async def work_with_extensions():
async with AIAgent(
...,
enabled_toolsets=["file", "skills", "memory", "mcp-project"],
) as agent:
return await agent.chat("Read the project skill and continue the task.")
This example assumes a configured MCP server named project; its canonical
toolset is mcp-project. Put skills below
$HERMES_HOME/skills/<name>/SKILL.md or configure
skills.external_dirs. Configure MCP servers under mcp_servers and memory
under memory in $HERMES_HOME/config.yaml. The first awaited agent boundary
loads skills/plugins and discovers MCP tools. Agent shutdown closes the MCP
lease and the selected memory provider.
The built-in file-backed memory exposes the memory tool. An external memory
provider is selected with memory.provider; install that provider's optional
extra when its package metadata requires one. Skills and memory are mutable
application state, so isolate HERMES_HOME between untrusted tenants.
See Skills, Memory, and MCP Configuration.
Service integration
No FastAPI application is bundled. A host framework should own startup, shutdown, authentication, quotas, and request routing while awaiting the library directly:
from fastapi import FastAPI
from run_agent import AIAgent
app = FastAPI()
@app.post("/chat")
async def chat(message: str):
# Isolated-job example. A production chat service should keep one agent
# per conversation ID and close it when that conversation expires.
async with AIAgent(...) as agent:
return await agent.run_conversation(message)
FastAPI is intentionally a downstream example, not a dependency. Sharing one global instance would both mix unrelated mutable conversation state and serialize every request.