Postgres + pgvector memory provider for hermes-agent. A shared memory substrate for a fleet of cooperating hermes-agent minions — built on Postgres and a single embedding endpoint you probably already run, with no LLM in the memory hot path.
each minion → X-Hermes-Session-Key: <theme>
→ hermes-agent gateway
→ pgvector plugin
├── memory_entries (mirrors built-in MEMORY.md / USER.md per theme)
└── conversations (every substantive turn, semantically searchable)
Existing memory providers each solve a piece of the problem; the gap for fleet deployments is wide:
- Built-in
memorytool persists to per-hostMEMORY.md/USER.md. Two minions on the same host stomp on each other; minions on different hosts have no shared substrate. - Honcho offers cross-session user modelling but requires a full external service, an LLM in the memory hot path for its deriver + dialectic loops, and its own ontology layered on top of the built-in tool. In high-concurrency fleet use it produces retry storms, embedding-endpoint queue backups, and gateway↔Honcho circular dependencies.
- Holographic is a fine in-process fact store but uses SQLite — a poor fit for many minions writing concurrently from many hosts.
- Other providers (Mem0, Hindsight, OpenViking, ByteRover, RetainDB, Supermemory) all either require a paid cloud, require LLM mediation for memory ops, or both.
What was missing: a storage layer that gives the built-in memory model durable, multi-tenant, semantically-searchable backing, with no LLM in the hot path, scoped cleanly per-minion so a marketing agent's notes don't pollute a trading agent's recall. That's what this plugin provides.
- Storage layer, not a memory model. The agent keeps using
memory(action='add', target='memory'|'user', …). We mirror those writes viaon_memory_write. No new ontology for the agent to learn. - No LLM in the memory hot path. Embeddings are vector math, not LLM calls. There is no deriver, no dialectic, no dream cycle.
- Per-agent themes by default, cross-theme recall on explicit demand. Every row carries
agent_identity. Recall is scoped to the current theme unless the agent asks forscope='all'. - Fail-soft everywhere. Embed endpoint down → degrade to text-only writes. Async writer queue full → drop with a one-time warning. DB down → log + skip. No exception escapes into the agent loop.
- Admin/runtime separation. DDL runs once with superuser via
hermes-pgvector migrate; the runtime role gets DML only on the migrated schema.
| Hook / surface | Behavior |
|---|---|
initialize() |
Verifies schema, opens a psycopg_pool.ConnectionPool, bulk-imports existing MEMORY.md + USER.md content. |
on_memory_write(action, target, content, meta) |
Mirrors built-in memory writes into memory_entries (add / replace / remove). |
sync_turn(user, assistant, session_id) / on_session_end(messages) |
Captures every substantive chat turn into conversations; on_session_end is a dedup'd backstop. |
on_delegation(task, result, …) |
Records parent→child delegation provenance (memory_agent_edges) and stores the exchange as a recallable turn. memory_agent_edges.child_identity is only filled when the host passes a child identity; current upstream hermes-agent passes only the task, result and child_session_id, so it is NULL there (child_session_id is populated). |
prefetch(query) / queue_prefetch(query, session_id=...) |
Ambient recall: top-K similar memory_entries for the current theme, injected into the system prompt. queue_prefetch pre-computes it off the agent thread. |
recall_memory(query, scope, target, limit) tool |
Explicit cross-theme search of durable memory entries. |
recall_conversation(query, scope, limit) tool |
Explicit search over past chat turns. scope ∈ {current, session, all, <theme>}. |
Operator maintenance CLI: hermes-pgvector migrate · stats · backfill · prune · cleanup · remap (destructive commands default to dry-run) — see docs/operations.md.
pip install hermes-memory-pgvector
hermes-pgvector install # writes $HERMES_HOME/plugins/pgvector/ (older hosts only)
# Apply ALL migrations (schema + attribution + FTS + runtime grants + md5 unique index)
hermes-pgvector migrate --admin-dsn \
"dbname=<your-memory-db> user=postgres host=/var/run/postgresql"
# add --runtime-role NAME if your runtime role isn't `hermes`
hermes config set memory.provider pgvector
sudo systemctl restart hermes.service
hermes memory status # expect: Provider: pgvector; Status: availableWhy the shim? hermes-agent resolves a provider from bundled dirs, then $HERMES_HOME/plugins/<name>/, then the hermes_agent.memory_providers pip entry-point group (declared by this package since v0.5.0). On a host that reads the entry point, pip install alone is enough and install is a no-op you can skip; the shim remains for older hosts that only scan directories. A shim directory always wins over the entry point when one is present.
git clone https://github.com/andreab67/hermes-memory-pgvector.git
cd hermes-memory-pgvector
./scripts/install.shInstalls the pinned Python deps, copies hermes_pgvector/ into $HERMES_HOME/plugins/pgvector/, and prints the migrate/activate commands.
# Python deps
pip install 'psycopg[binary]>=3.3.6,<4' 'psycopg-pool>=3.3.3,<4' 'PyYAML>=6.0,<7'
# Plugin module
mkdir -p ~/.hermes/plugins
cp -r hermes_pgvector ~/.hermes/plugins/pgvectorThen apply migrations and activate as in Option 1 above (hermes-pgvector migrate --admin-dsn ..., or psql -f each hermes_pgvector/migrations/*.sql in order — see docs/operations.md for the full admin walkthrough and docs/upgrading.md if you're coming from an older version).
Lives in $HERMES_HOME/config.yaml under plugins.pgvector, every key optional:
plugins:
pgvector:
dsn: "dbname=hermes_memory user=hermes host=/var/run/postgresql"
embed_url: "http://localhost:11434"
embed_model: "nomic-embed-text"
allowed_themes: "marketing,sales,trading" # empty/unset = allow any themeFull reference for every key (type, default, since-version, effect): docs/configuration.md. Scaling guidance (HNSW tuning, pool sizing, embed timeouts): docs/scaling.md.
Each systemd-run minion sets one header on its OpenAI client; everything else flows automatically:
client = AsyncOpenAI(
base_url="http://127.0.0.1:8642/v1",
api_key=API_KEY,
default_headers={"X-Hermes-Session-Key": "marketing"}, # ← theme
)The gateway plumbs X-Hermes-Session-Key through as gateway_session_key=… in MemoryProvider.initialize kwargs, taking priority over the profile default so unprofiled API traffic doesn't collapse every minion into one shared default scope.
Identity resolution order in initialize: gateway_session_key > agent_identity (unless it is default) > agent_workspace > default. Upstream hermes-agent always passes agent_workspace="hermes" (and agent_identity=<profile name>), so a default-profile CLI or cron session with no gateway session key lands in the theme hermes, not default; named profiles land in their profile name. Set the X-Hermes-Session-Key header (or a non-default profile) if you want a scope other than hermes. When allowed_themes is set, hermes and your profile names are not exempt: include them in the list if you want those sessions to keep their own theme, otherwise they fall back to default.
Convention: lowercase, dash-separated, stable (identities are case-folded automatically — Marketing and marketing are the same theme). Governed sinks that always exist regardless of your themes: whatsapp-dm (collapsed DM/session keys), external-group (collapsed group/channel/thread keys), _bench (benchmark traffic), default (only when the host passes no identity kwargs at all). Set plugins.pgvector.allowed_themes to your product/worker list to enforce an allow-list — an unknown or typo'd header then falls back to default (one-time warning) instead of silently minting a new theme.
This plugin follows semantic versioning on its public surface, stable as
of 1.0.0:
plugins.pgvector.*config keys (see docs/configuration.md)- Tool names and parameters (
recall_memory,recall_conversation) - CLI commands, flags, and exit codes (
hermes-pgvector ...) - Database table/column names
- The
pgvectorprovider name and the package's pip entry point
Within the 1.x series this surface is guaranteed stable: it may grow (new
config keys, new optional params), but nothing already listed is renamed or
removed, and no default changes behaviour, without a 2.0 release.
MemoryStore and every other Python class/module are internal — not covered by semver, safe to change between minor releases, and excluded from the 1.x guarantee above. A migration file, once released, is never edited in place; schema changes land as a new numbered migration.
Tested in CI on every push/PR:
| Component | Versions |
|---|---|
| Python | 3.11, 3.12, 3.13 |
| PostgreSQL | 16, 17, 18 |
| pgvector extension | >= 0.5.0 required; CI uses the version bundled in the pgvector/pgvector:pg1x images |
| hermes-agent | Conformance-tested against the pinned ref in conformance/HERMES_AGENT_REF, plus a non-blocking weekly drift check against upstream main |
- docs/configuration.md — every config key
- docs/operations.md — migrate, backfill, prune, cleanup, remap, stats, alerting
- docs/scaling.md — HNSW tuning, pool sizing, embed latency budgets
- docs/troubleshooting.md — common failures and fixes
- docs/upgrading.md — version-to-version upgrade procedures
- docs/release/RELEASING.md — maintainer release checklist
- CHANGELOG.md — every release, in Keep a Changelog format
- ROADMAP.md — milestones, what shipped where, what's deliberately not planned
- SECURITY.md — supported versions, how to report a vulnerability
pip install -e ".[test]"
pytest -q # no DB/embed endpoint: everything skips gracefully
# Live mode (against a throwaway Postgres + your embed endpoint)
export PG_TEST_DSN='dbname=hermes_test user=postgres host=/var/run/postgresql'
export PG_TEST_EMBED_URL='http://your-embed-endpoint:11434'
# Superuser DSN for the same database: the migration, CLI and empty-content
# tests skip without it, and CI fails on any skip.
export PG_TEST_ADMIN_DSN='dbname=hermes_test user=postgres host=/var/run/postgresql'
pytest -qThe simplest path is the bundled harness (Postgres + pgvector in Docker and a fake embed server), which prints all the variables for you:
scripts/test-env.sh up pg17
eval "$(scripts/test-env.sh db hermes_test)"
pytest -qhermes config set memory.provider none
sudo systemctl restart hermes.service
# Optional — drop every plugin-owned object (data loss, irreversible). Their
# sequences and indexes are dropped automatically with the tables; DROP VIEW
# must run before the tables it selects from.
sudo -u postgres psql -d <your-memory-db> -c "
DROP VIEW IF EXISTS v_agent_memory;
DROP TABLE IF EXISTS memory_maintenance_log;
DROP TABLE IF EXISTS memory_agent_edges;
DROP TABLE IF EXISTS memory_agents;
DROP TABLE IF EXISTS conversations;
DROP TABLE IF EXISTS memory_entries;
"
# Optional — remove the plugin files
rm -rf ~/.hermes/plugins/pgvectorPer the hermes-agent CONTRIBUTING.md: the set of built-in memory providers is closed, and new backends are expected to ship as standalone plugins installed into ~/.hermes/plugins/ or via a pip entry point — exactly what this package does.
Bug reports + PRs welcome. Open an issue describing the failure mode + your environment (hermes-agent version, Postgres version, embed endpoint), or a PR with a focused change + test.
BSD 3-Clause © 2026 Green Yoga Inc