Memory & Knowledge v1.0.0 MIT
Graphiti Memory: Diagnosis and Tuning
Use when diagnosing Graphiti timeouts or pool pressure. It also applies to hangs, keepalive drops and writes that fail with missing credentials. Covers the tuning ladder and the event-loop and embedder traps.
Features
- A health check of the Neo4j listeners and the HTTP query endpoint, before any tuning.
- A check of the server's live configuration, so no key name is guessed.
- A four-step fix ladder ordered by blast radius.
- An explicit embedder argument for Graphiti when the default embedder needs a provider key you do not use.
- A driver-result access note for ad-hoc queries.
Benefits
- Tuning starts from the live configuration, not from a remembered key name.
- Low-risk fixes come first, so restarts and code changes are a last resort.
- Silent skips show up in logs, instead of looking like success.
Install
From the tap (or GitHub path)
hermes skills install shagghiesuperstar/hermes-skills/skills/graphiti-memory --category memory-knowledge
Direct URL
hermes skills install https://raw.githubusercontent.com/shagghiesuperstar/hermes-skills/main/skills/graphiti-memory/SKILL.md --category memory-knowledge
Get the skill
Related skills
- Memory & KnowledgeTriple-Threat MemoryUse when Hermes runs three bridged memory stores. They are a primary fact store, a local semantic index and a temporal graph. Route each read and write to the right store, keep the stores consistent with an idempotent bridge, and report partial writes honestly.
- Memory & KnowledgeMemory Stack SnapshotUse when snapshotting a Hermes memory stack. It covers profile state, Hindsight native state, the Omega SQLite store, the Graphiti MCP configuration and source, and the Neo4j graph, and restoring is covered too. Verify each layer before relying on the snapshot.
- Memory & KnowledgeHindsight Long-Term MemoryUse when storing or retrieving long-term agent memories. It also covers the Hindsight daemon, retain failures, cloud or local mode, and bridges to another store.
Related repos
The skill file
Graphiti Memory: Diagnosis and Tuning
Overview
Graphiti is a temporal knowledge-graph memory that stores its data in Neo4j and is often reached through an MCP server. Its common failures look alike from the agent's side: a call hangs past a minute, a task is cancelled at a fixed timeout, or a write is silently skipped. The causes differ, and each has a different fix. This skill sets the order for diagnosing them, the tuning ladder from the lowest-risk change upward, and the traps that the upstream documentation does not warn about.
When to Use
- The Graphiti MCP tool returns a cancellation error or hangs for more than about 60 seconds.
- The Neo4j log shows bolt connector or pool errors.
- A scheduled memory-bridge job retries without reporting why.
- An operator asks to raise a timeout, a pool size or a job's cadence.
- A Graphiti write fails with a message about missing credentials, while the rest of the system works.
Features
- A health check of the Neo4j listeners and the HTTP query endpoint, before any tuning.
- A check of the server's live configuration, so no key name is guessed.
- A four-step fix ladder ordered by blast radius.
- An explicit embedder argument for Graphiti when the default embedder needs a provider key you do not use.
- A driver-result access note for ad-hoc queries.
Benefits
- Tuning starts from the live configuration, not from a remembered key name.
- Low-risk fixes come first, so restarts and code changes are a last resort.
- Silent skips show up in logs, instead of looking like success.
Procedure
1. Record the failure
Write down the timestamp, the exact error text (cancellation, bolt error, or authentication), and what triggered the call: a scheduled job, a new session, or an agent request.
2. Check Neo4j health
Check both listeners, then run a trivial query over the HTTP query endpoint:
nc -z 127.0.0.1 7687 && echo "bolt UP" || echo "bolt DOWN"
nc -z 127.0.0.1 7474 && echo "http UP" || echo "http DOWN"
curl -s --netrc-file <netrc-file> \
-X POST http://127.0.0.1:7474/db/neo4j/query/v2 \
-H "Content-Type: application/json" \
-d '{"statement": "RETURN 1 AS n"}'
Read the password from the secret store that holds it, and write it into the netrc file, which must be readable only by you. The netrc file has a machine 127.0.0.1 entry with the login and the password. Do not put the password in a script, on a command line, or in a shared note.
3. Read the live pool and timeout settings
Query the server's configuration, and only then pick a setting name:
curl -s --netrc-file <netrc-file> \
-X POST http://127.0.0.1:7474/db/neo4j/query/v2 \
-H "Content-Type: application/json" \
-d '{"statement": "CALL dbms.listConfig() YIELD name, value WHERE name CONTAINS \"pool\" RETURN name, value"}'
Use a name exactly as the server returns it. On Neo4j 2026.05.0, the server-side bolt connection-pool setting does not exist. Older guides still list it, and setting it makes the server refuse to start. Revert any configuration file you changed, from its backup.
Read the MCP client's timeout settings from the profile's configuration file, with a parser rather than a text search.
4. Find other writers on the same schedule
List the scheduled jobs whose names mention memory, Graphiti or the other memory layers. Several jobs that run every few minutes on the same quarter-hour all open pool connections at once.
5. Apply the fix ladder, lowest blast radius first
- Stagger the schedules. Move competing jobs to different minutes in the scheduler's file. Back up the file first, and change only the schedule expression. The scheduler picks up the change on its next tick, so no restart is needed.
- Raise the MCP connect timeout. Increase the client-side connect timeout in the profile's MCP entry. The MCP server reads it when the gateway spawns it, so the gateway must respawn the server. Restart the gateway through its own service manager, not by killing a process ID in a shell.
- Lower in-process concurrency. Reduce the Graphiti semaphore limit, which sets how many tasks in one process compete for pool connections. A high limit against a rate-limited model provider also causes 429 errors, so lower it there too.
- Raise the client-side driver pool. The starving pool is the client driver's, not the server's. The Neo4j Python driver defaults to a pool of 100 connections and a 60-second acquisition timeout. Raise them where the driver is constructed, for example with
max_connection_pool_sizeandconnection_acquisition_timeout. If the Graphiti library's factory builds the driver, patch that factory, because no configuration file exposes the value. - Change the server's asyncio timeout in code. This is the highest blast radius. Edit only after the steps above, with a backup and a test run, because the timeout is not exposed in any configuration file.
6. Check the event loop before blaming the pool
Graphiti's async driver binds its sockets to the event loop that created them. Two patterns fail in different callers:
- Calling
asyncio.run()for each call fails on the second call with an "attached to a different loop" error. - Running a persistent loop with
run_until_completeon the caller's thread works from a plain script, but fails inside a server whose tools run on a loop-owning thread, with "cannot run the event loop while another loop is running".
A pattern that works from every caller: one persistent loop on a dedicated daemon thread, with each coroutine submitted by asyncio.run_coroutine_threadsafe(coro, loop).result(timeout).
7. Pass the embedder explicitly
The Graphiti constructor falls back to a default embedder when the embedder argument is empty. That fallback needs an OpenAI key in the environment, and it fails with "Missing credentials" when the system uses a different model provider. Check each of these:
- Pass
llm_client,embedderandcross_encoderexplicitly. When the LLM provider is routed through an OpenAI-compatible endpoint, the default embedder cannot use that provider's credentials. - Use a local embedder class that implements the same embed call, when no embedding endpoint is available from the provider.
- Check that the environment file does not set the key to an empty string. An empty value counts as set, so the fallback runs.
- If the environment is loaded in a parent shell and used in a child process, the variables may not reach the child. Load them in the process that builds the client.
8. Log silent skips at warning level
Code that skips Graphiti after an error must log that skip at warning level, not debug level. A skip logged at debug level hides the fact that a memory server is running without its graph store. Log the skip at warning level, so that the per-source result list shows the gap.
Fix reference
| Symptom | Likely cause | Action |
|---|---|---|
| Cancellation at exactly the connect timeout | Pool starved, or timeout too short | Steps 5.1 to 5.4 |
| Server refuses to start after a configuration edit | Setting name not valid on this server version | Revert the file, then use names from step 3 only |
| Bolt port not listening | Still booting (allow 30 to 90 seconds), or a configuration error | Read the server log |
| Service fails to start with an I/O error from the service manager | A bootstrap race from an earlier failed start | Stop the service, wait a few seconds, start it again |
| MCP process not running | Normal: the gateway starts it on first use | Call the tool again |
| Authentication failure on bolt | Wrong password | Read the password from the secret store |
| 429 from the model provider | Concurrency above the provider's tier | Lower the semaphore limit |
Neo4j driver result access
Driver records may not support key access on every version. A numeric index always works, and converting the record to a plain dictionary first is the safest form:
from neo4j import GraphDatabase
driver = GraphDatabase.driver("bolt://localhost:7687", auth=("neo4j", "<password>"))
with driver.session() as s:
result = s.run("MATCH (e:Episodic) RETURN e.group_id AS gid, count(*) AS c")
for record in result:
r = dict(record)
print(r["gid"], r["c"])
Pitfalls
- Guessed configuration names. A name taken from an old guide can be removed in a new server version. Read the names from the live server.
- Pool fixes that do not reach the process. A pool setting in the server's file does nothing for a starved client pool.
- Restarts by process ID. The supervisor starts the process again, so a kill is not a restart.
- Staggering without measuring. Schedules that overlap at the same quarter-hour are the cheapest thing to fix, so fix them first, then measure.
Verification
- Both Neo4j listeners answer, and the query endpoint returns the trivial result.
- The live pool and timeout settings were read from the server, and each changed setting is listed with its old and new value.
- A test call through the MCP tool completes inside the new timeout.
- The scheduled jobs no longer share a minute.
Attribution
Graphiti is an open-source project by Zep. Its MCP server documentation is at https://github.com/getzep/graphiti/blob/main/mcp_server/README.md, and its example configuration is at https://github.com/getzep/graphiti/blob/main/mcp_server/config/config.yaml. The Neo4j connector settings reference is at https://neo4j.com/docs/operations-manual/current/configuration/connectors/. Treat the upstream examples as a starting point, and check each setting name against the live server.