Sessions
An AIAgent keeps its live conversation history across sequential calls. To
persist that history across agent instances or processes, attach a SessionDB
explicitly.
In-memory lifecycle
from run_agent import AIAgent
async def in_memory_conversation():
async with AIAgent(...) as agent:
await agent.chat("My project uses Python 3.12.")
return await agent.chat("Which Python version did I mention?")
One instance represents one ordered conversation. Concurrent turns on that instance are serialized; separate instances can run concurrently.
Enable durable storage
from hermes_state import SessionDB
from run_agent import AIAgent
async def stored_conversation():
db = SessionDB("./state.db")
async with AIAgent(
...,
session_db=db,
session_id="project-review",
) as agent:
return await agent.run_conversation("Review this project.")
SessionDB uses aiosqlite. Its constructor is state-only; the connection and
schema initialize on the first awaited operation. Passing it explicitly starts
durable transcript persistence at the turn prologue and selects the database
path. A recall tool may otherwise open the default $HERMES_HOME/state.db
lazily. An injected store is borrowed by the agent; the host that created it
closes it once during worker shutdown. Only a lazily created default store is
owned by the agent. SessionDB.close() is idempotent.
Use PostgreSQL for a service
SQLite remains the default and is a good fit for a single-process application.
For a service whose workers share a durable store, install the optional
PostgreSQL backend and inject it through the same existing session_db=
argument:
uv sync --extra postgres
from hermes_state_postgres import SessionDB
from run_agent import AIAgent
async def postgres_conversation():
db = SessionDB(
"postgresql+asyncpg://user:password@db.example:5432/hermes",
)
try:
async with AIAgent(
...,
session_db=db,
session_id="project-review",
) as agent:
return await agent.run_conversation("Review this project.")
finally:
await db.close()
The PostgreSQL SessionDB keeps the SQLite method names and awaited calling
style; only the import and explicit DSN change. A store reads the active
profile's database.postgres pool and driver settings once when its first
database operation initializes the engine. Create a new store after changing
those settings. See SessionDB storage and PostgreSQL settings
for the supported options. Writable initialization creates or additively
reconciles the retained tables and indexes; read-only initialization never
migrates an existing database.
For a read-only endpoint, pass read_only=True:
readonly_db = SessionDB(read_replica_url, read_only=True)
This blocks SessionDB writes and enables PostgreSQL transaction read-only mode;
it does not choose a replica automatically. In a multi-worker service, create
and close one store per worker lifespan and share it with that worker's agents.
Plan for a possible connection count of
workers * (pool_size + max_overflow). Other retained stores, such as memory
plugin databases, remain separate from the core PostgreSQL SessionDB.
Resume in a new agent
Passing only the old session_id does not automatically load its messages.
Resolve the current compression descendant, load model history, and provide it
to run_conversation():
from hermes_state import SessionDB
from run_agent import AIAgent
async def resume_conversation():
db = SessionDB("./state.db")
tip = await db.resolve_resume_session_id("project-review")
model_history, display_history = await db.get_resume_conversations(tip)
async with AIAgent(
...,
session_db=db,
session_id=tip,
) as agent:
result = await agent.run_conversation(
"Continue from the saved review.",
conversation_history=model_history,
)
return result, display_history
model_history is the alternation-repaired history fed to the model.
display_history includes the full ancestor-to-tip lineage for applications
that render a transcript.
Search and compression
The store retains message order, structured tool calls, reasoning, display
metadata, and compression lineage. Its FTS-backed search powers the optional
session_search toolset:
async def search_prior_sessions():
async with AIAgent(
...,
session_db=SessionDB("./state.db"),
enabled_toolsets=["session_search"],
) as agent:
return await agent.chat("Find the earlier deployment discussion.")
Context compression can end one database session and continue in a linked child
session. Always call resolve_resume_session_id() before a cross-process
resume so post-compression messages are not missed.
Cancellation of an active conversation attempts a crash-safe partial persist
before propagating CancelledError. This protects recoverability but does not
replace application-level backups or SQLite filesystem durability planning.