Pixel Rainbow

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 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
  1. 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.
  2. 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.
  3. 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.
  4. 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_size and connection_acquisition_timeout. If the Graphiti library's factory builds the driver, patch that factory, because no configuration file exposes the value.
  5. 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_complete on 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, embedder and cross_encoder explicitly. 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
SymptomLikely causeAction
Cancellation at exactly the connect timeoutPool starved, or timeout too shortSteps 5.1 to 5.4
Server refuses to start after a configuration editSetting name not valid on this server versionRevert the file, then use names from step 3 only
Bolt port not listeningStill booting (allow 30 to 90 seconds), or a configuration errorRead the server log
Service fails to start with an I/O error from the service managerA bootstrap race from an earlier failed startStop the service, wait a few seconds, start it again
MCP process not runningNormal: the gateway starts it on first useCall the tool again
Authentication failure on boltWrong passwordRead the password from the secret store
429 from the model providerConcurrency above the provider's tierLower 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.