
Security News
Happy Birthday, Shai-Hulud
It has been one year since Shai-Hulud made its first appearance on npm.
@knowall-ai/reverie
Advanced tools
Reverie — graph memory that dreams. Graph-based memory system for AI agents. Store people, places, organizations as nodes. Build semantic relationships (KNOWS, WORKS_AT, CREATED). Hybrid keyword + semantic search finds partial matches and close variants.
Reverie is KnowAll AI's Neo4j knowledge-graph memory for AI agents, served over MCP. Until @knowall-ai/reverie is published to npm, install the legacy package @knowall-ai/mcp-neo4j-agent-memory (0.2.x). The GitHub repository is now knowall-ai/mcp-reverie, and old URLs redirect.

Reverie turns an agent's memory from a pile of facts into a map of the entities in its world and how they relate, and keeps that map healthy. It is an MCP server, so any agent that speaks the Model Context Protocol (Claude Desktop, OpenClaw, Azure AI Foundry, Cursor…) gets the same graph; a Hermes Agent memory-provider flavour lives in hermes-reverie.
dream tool merges duplicates safely, canonicalises labels, re-embeds, counts orphans and flags nodes that have become property dumps, so a nightly job can keep the graph clean.You can run this MCP server directly using npx:
npx @knowall-ai/mcp-neo4j-agent-memory # becomes `npx @knowall-ai/reverie` once 0.4.0 is published
Or add it to your Claude Desktop configuration:
{
"mcpServers": {
"neo4j-memory": {
"command": "npx",
"args": ["@knowall-ai/mcp-neo4j-agent-memory"],
"env": {
"NEO4J_URI": "bolt://localhost:7687",
"NEO4J_USERNAME": "neo4j",
"NEO4J_PASSWORD": "your-password",
"NEO4J_DATABASE": "neo4j"
}
}
}
}
Unlike traditional approaches that embed complex logic in tools, this server provides simple, atomic operations and lets the LLM handle all the intelligence:
search_memories is hybrid: keyword hits (any word of the query as a substring of any searchable content property) rank first, then semantic matches above a similarity threshold. See Search below.
This approach makes the system more powerful and adaptable, as improvements in LLM capabilities directly translate to better memory management.
search_memories now supports three modes:
hybrid (default): keyword hits score 1, then semantic matches add close variants such as Benjamin Weeks for Ben Weekskeyword: any word of the query as a substring of any searchable content property (timestamps, status and embedding fields are never matched)semantic: uses embeddings only when available, with graceful fallback to keyword behavior if embeddings are unavailableexact: case-insensitive equality on name, aliases or email; the precise lookup to run before creating a memory, so "ben weeks" finds exactly "Ben Weeks" and nothing elseArchived memories (status = 'archived') are left out of results and of list_memory_labels unless include_archived: true is passed. Returned relationships carry _start and _end node ids, so a connection's direction is always recoverable.
Use similarity_threshold (default 0.4; must be between 0 and 1, other values are rejected) to control how strict semantic matches are. Results include _score and _match on each returned memory object so callers can explain why a memory was returned.
This server now supports connecting to specific databases in Neo4j Enterprise Edition. By default, it connects to the "neo4j" database, but you can specify a different database using the NEO4J_DATABASE environment variable.
search_memories: Search and retrieve memories from the knowledge graph
Ben Weeks can also find Benjamin Weekssearch_mode = hybrid, keyword, or semanticsimilarity_threshold (default 0.4)_score and _match metadataperson and Person both work), date, depth, result limit, and sort ordercreate_memory: Create a new memory in the knowledge graph
dream canonicalises lowercase labelscreate_connection: Create relationships between memories
update_memory: Update properties of existing memories
_hint when the node exceeds the property limit: the graph is for entities and relationships, not a notebookupdate_connection: Update relationship properties
delete_memory: Remove memories and all their connections
delete_connection: Remove specific relationships
list_memory_labels: List all unique memory labels in use
query_memories: Run a read-only Cypher query
CALL subqueries, and any procedure outside a small read-only allow-list (db.labels, db.propertyKeys, db.index.*.query*, schema procedures). For belt and braces, run the server with a read-only Neo4j role where you canmemory_stats: Summarize the current graph
dream: Deterministically clean up and consolidate the graph
person → Person), merges same-named nodes within a label when APOC is available, and refreshes embeddingsname, timestamps and vectors and combines every other property (conflicting values become lists, nothing is dropped) and skips pairs whose identity fields differ (email, phone, website, company, organisation, organization), so two different "John Smith"s stay separate. The report lists every group under duplicates with what was merged and what was skipped and why. Run with dry_run: true first to reviewbloated nodes (more than REVERIE_MAX_PROPERTIES, default 30, real properties) with the keys that look like dated facts or prose, so a nightly sleep can fold them into attributes, relationships or notesdry_run for a no-write reportget_guidance: Get help on using the memory tools effectively
Neo4j Database 5.9 or newer (the dream tool uses COUNT {} and IS :: STRING); APOC for duplicate merging
docker run -p 7474:7474 -p 7687:7687 -e NEO4J_AUTH=neo4j/password neo4jNode.js (v18 or higher)
Claude Desktop (for MCP integration)
Reverie is listed in the MCP Registry as ai.knowall/reverie (KnowAll's domain namespace, verified by a DNS TXT record on knowall.ai) (the successor of the "Neo4j Agent Memory" entry that used to sit in the modelcontextprotocol/servers README). server.json in this repository is the listing; the release workflow republishes it on every tag.
To install Reverie for Claude Desktop automatically via Smithery:
npx -y @smithery/cli install @knowall-ai/mcp-neo4j-agent-memory --client claude
git clone https://github.com/knowall-ai/mcp-reverie.git
cd mcp-reverie
npm install
npm run build
The server requires the following environment variables:
NEO4J_URI: Neo4j database URI (required, e.g., bolt://localhost:7687)NEO4J_USERNAME: Neo4j username (required)NEO4J_PASSWORD: Neo4j password (required)NEO4J_DATABASE: Neo4j database name (optional) - For Neo4j Enterprise with multiple databasesSet REVERIE_EMBEDDINGS to choose the embedding provider used by hybrid and semantic search. Changing REVERIE_EMBEDDINGS or REVERIE_EMBEDDING_MODEL causes nodes to be re-embedded lazily on the next search, or eagerly when you run dream.
REVERIE_EMBEDDINGS | Default model | Required env vars |
|---|---|---|
local (default) | Xenova/all-MiniLM-L6-v2 | none; optional REVERIE_MODEL_CACHE |
openai | text-embedding-3-small | OPENAI_API_KEY; optional OPENAI_BASE_URL |
azure | deployment-backed | AZURE_OPENAI_ENDPOINT, AZURE_OPENAI_API_KEY, AZURE_OPENAI_EMBEDDING_DEPLOYMENT; optional AZURE_OPENAI_API_VERSION |
ollama | nomic-embed-text | optional OLLAMA_HOST |
voyage | voyage-3-lite | VOYAGE_API_KEY |
none | disabled | none |
Use REVERIE_EMBEDDING_MODEL to override the model name for any provider. Remote providers batch up to 64 texts per request, time out after REVERIE_EMBED_TIMEOUT_MS (default 30000, max 120000), and must be reached over https; plain http is only accepted for localhost.
The local provider downloads its model (about 23 MB) from Hugging Face on first use and caches it. If that download fails, or any provider errors, search degrades to keyword matching for that call and the error is logged to stderr. Set REVERIE_EMBEDDINGS=none to turn embeddings off entirely.
Each node stores two vectors: embedding (label, name and every text property) and name_embedding (label, name and aliases only), plus embedding_model and embedded_at. A semantic score is the better of the two, so a short query like Ben Weeks still matches a richly described Benjamin Weeks. None of these fields are ever returned by the tools.
Copy .env.example to .env and update with your credentials:
cp .env.example .env
# Edit .env with your Neo4j credentials
Add the environment variables to your Claude Desktop configuration (see Quick Start above).
The LLM handles all the complexity of deciding what to store and how to connect memories:
User: "Remember that John works at Google as a software engineer"
Assistant: I'll remember that John works at Google as a software engineer.
[Behind the scenes, the LLM does:
1. search_memories({"query": "John"}) - Check if John exists
2. search_memories({"query": "Google"}) - Check if Google exists
3. create_memory({"label": "Person", "properties": {"name": "John", "occupation": "software engineer"}})
4. create_memory({"label": "Organization", "properties": {"name": "Google"}})
5. create_connection({"fromMemoryId": 123, "toMemoryId": 456, "type": "WORKS_AT"})]
User: "Remember that Sarah is John's manager and she started in 2019"
Assistant: I'll add that information about Sarah.
[The LLM searches for John, creates Sarah, and connects them with a MANAGES relationship including the start date]
User: "Who works at Google?"
Assistant: Let me search for people who work at Google.
[LLM uses: search_memories({"query": "Google", "depth": 2})]
Based on my memory:
- John - Software Engineer at Google
- Sarah - Manager at Google (manages John, started 2019)
User: "What do you remember about John?"
Assistant: Here's what I know about John:
[LLM uses: search_memories({"query": "John", "depth": 2})]
- Works at Google as a Software Engineer
- Sarah is his manager
- [Any other connected memories found in the search]
User: "John and Sarah are working on Project Atlas together"
Assistant: I'll connect John and Sarah to Project Atlas.
[The LLM:
1. Searches for John and Sarah to get their IDs
2. Creates Project Atlas: create_memory({"label": "Project", "properties": {"name": "Project Atlas"}})
3. Connects them: create_connection({"fromMemoryId": johnId, "toMemoryId": projectId, "type": "WORKS_ON"})
4. create_connection({"fromMemoryId": sarahId, "toMemoryId": projectId, "type": "WORKS_ON"})]
For Neo4j Enterprise users with multiple databases, you can specify which database to connect to:
{
"env": {
"NEO4J_URI": "bolt://localhost:7687",
"NEO4J_USERNAME": "neo4j",
"NEO4J_PASSWORD": "your-password",
"NEO4J_DATABASE": "myCustomDatabase"
}
}
This will connect to "myCustomDatabase" instead of the default "neo4j" database.
User: "Show me all employees in the Sales department"
Assistant: Let me search for employees in the Sales department.
[LLM uses: search_memories({"query": "Sales department", "depth": 2})]
I found these employees in the Sales department:
- John Smith (Sales Manager)
- Sarah Johnson (Sales Representative)
- Mike Brown (Account Executive)
User: "Create a product called 'Premium Coffee' with price $24.99"
Assistant: I'll create that product for you.
[LLM uses: create_memory({"label": "Product", "properties": {"name": "Premium Coffee", "price": 24.99}})]
Created Premium Coffee product with ID: 456
User: "The Premium Coffee uses beans from our Colombian Supplier"
Assistant: I'll connect Premium Coffee to the Colombian Supplier.
[LLM:
1. search_memories({"query": "Premium Coffee"})
2. search_memories({"query": "Colombian Supplier"})
3. create_connection({"fromMemoryId": 456, "toMemoryId": 789, "type": "SUPPLIED_BY"})]
Connected Premium Coffee to Colombian Supplier
The LLM can handle complex queries by intelligently using the search tools:
User: "Find Ben Weeks"
Assistant: I'll search for memories containing "Ben" or "Weeks".
[LLM uses: search_memories({"query": "Ben Weeks"})]
I found several people matching your search:
- Ben Weeks - Software Engineer at TechCorp (exact match)
- Sarah Ben - Data Scientist at DataCo (contains "Ben")
- John Weeks - Project Manager (contains "Weeks")
The most likely match appears to be Ben Weeks, the Software Engineer.
Note: The search finds ANY word from your query, allowing the LLM to present all potentially relevant results and identify the best match based on context.
User: "What new people have I met in the last week?"
Assistant: I'll search for people added to your memory in the last 7 days.
[LLM calculates the date 7 days ago and uses:
search_memories({
"query": "",
"label": "person",
"since_date": "2024-01-20T00:00:00Z",
"order_by": "created_at DESC"
})]
Here are the new people you've met in the last week:
- Sarah Chen - Met at tech conference on Monday
- Dr. Michael Roberts - New physician, first appointment Tuesday
- Emma Wilson - Joined your project team on Wednesday
- James Park - Coffee meeting about potential collaboration Thursday
User: "What does John do?"
Assistant: I found multiple people named John. Let me show you what I know:
[LLM uses: search_memories({"query": "John", "type": "Person", "depth": 1})]
1. John Doe - Software Engineer at Google
2. John Smith - Sales Manager in Sales Department
3. John Brown - Project Manager on Project Atlas
Which John were you asking about?
User: "What events did I attend last month?"
Assistant: I'll search for event memories and check their dates.
[LLM uses: search_memories({"query": "", "label": "event", "sort_by": "created_at", "sort_order": "desc"})]
[Filters results to last month based on created_at or date properties]
Here are the events you attended last month:
- Tech Innovation Summit (March 5-6) - Keynote speaker
- Team Building Workshop (March 12) - With engineering team
- Client Dinner Meeting (March 18) - Project kickoff with ABC Corp
- WebDev Conference (March 25-27) - Attended 5 sessions on React
The system doesn't enforce strict types - you can create any type of memory that makes sense:
Common Types (Capitalised singular is canonical; lowercase is accepted and canonicalised by dream): person, place, organization, project, event, topic, object, animal, plant, food, activity, media, skill, document, meeting, task, habit, health, vehicle, tool, idea, goal
But you can use any type (any plain identifier): recipe, dream, memory, quote, book, movie, emotion, relationship, appointment, medication, exercise, symptom, payment, contract, etc.
The LLM will intelligently reuse existing types when appropriate to maintain consistency.
The true value of this memory system lies not just in storing individual memories, but in creating connections between them. A knowledge graph becomes exponentially more useful as you build relationships:
Always look for relationships when storing new information:
Bad: Just store "John is a developer"
Good: Store John AND connect him to his company, projects, skills, and colleagues
Use semantic relationship types that capture meaning:
WORKS_AT, MANAGES, KNOWS, LIVES_IN, CREATED, USES, LEARNED_FROM
Add relationship properties for richer context:
create_connection({
"fromMemoryId": 123,
"toMemoryId": 456,
"type": "WORKS_ON",
"properties": {"role": "Lead", "since": "2023-01", "hours_per_week": 20}
})
Think in graphs: When recalling information, use depth > 1 to explore the network:
search_memories({"query": "John", "depth": 3}) // Explores connections up to 3 hops away
Remember: A memory without connections is like a book in a library with no catalog - it exists, but its utility is limited. The more you connect your memories, the more intelligent and useful your knowledge graph becomes.
npm run build # TypeScript → build/
npm run test:unit # pure-module unit tests + server startup checks, no database needed
npm run test:integration # drives the built server over stdio against a live Neo4j
npm run test:coverage # everything under c8 with a coverage gate (70% lines and functions)
The integration tests need a Neo4j 5 with APOC and wipe the database they point at, so give them a disposable one:
docker run -d --rm --name reverie-test-neo4j -p 17687:7687 \
-e NEO4J_AUTH=neo4j/test-password -e NEO4J_PLUGINS='["apoc"]' neo4j:5-community
NEO4J_URI=bolt://127.0.0.1:17687 NEO4J_USERNAME=neo4j NEO4J_PASSWORD=test-password \
REVERIE_TEST_DESTRUCTIVE=1 npm run test:coverage
REVERIE_TEST_DESTRUCTIVE=1 is the explicit opt-in: without it the suite refuses to run. With it,
the suite wipes whatever NEO4J_URI points at, so only set it alongside a disposable database.
The suite waits up to a minute for authenticated Bolt before starting, and a remote NEO4J_URI
must use bolt+s:// or neo4j+s:// with a validated certificate (+ssc is refused).
CI runs the same suite against a Neo4j service container on every pull request and fails the build if coverage drops below the gate. The first semantic search downloads the local embedding model (about 23 MB); CI caches it.
For interactive testing and debugging, use the MCP Inspector:
# Quick start with environment variables from .env
./run-inspector.sh
# Or manually with specific environment variables
NEO4J_URI=bolt://localhost:7687 \
NEO4J_USERNAME=neo4j \
NEO4J_PASSWORD=your-password \
npx @modelcontextprotocol/inspector build/index.js
The inspector provides a web UI to:
reverie serve)reverie on its own is the stdio MCP server. reverie serve runs the same server over
Streamable HTTP
and adds a read-only Brain API that the KnowAll Agents Portal
Brain tab uses to draw the graph live. One bearer token protects both; only the health check is open.
REVERIE_SERVE_TOKEN=$(openssl rand -hex 24) NEO4J_PASSWORD=… reverie serve
# Reverie HTTP server listening on http://127.0.0.1:8643
| Variable | Default | Purpose |
|---|---|---|
REVERIE_SERVE_TOKEN | (required) | Bearer token clients must send as Authorization: Bearer … |
REVERIE_HTTP_HOST / REVERIE_HTTP_PORT | 127.0.0.1 / 8643 | Bind address; keep it on loopback behind a reverse proxy |
REVERIE_ALLOWED_ORIGINS | none | Comma-separated browser origins allowed to call the API (they get CORS headers and preflight answers); requests carrying any other Origin header get 403. Server-to-server clients send no Origin and need nothing here |
REVERIE_EVENTS_PATH | ~/.reverie/events.jsonl | Activation log (see below); empty string disables it |
REVERIE_DREAMS_DIR | unset | Optional dream diary folder (*.md); the newest file names the last dream |
REVERIE_USAGE_STATS_PATH / REVERIE_BOOST_STATE_PATH | ~/call-transcripts/usage-stats.json / boost-state.json | Presence HUD files passed through in state (usage only when written within 15 min) |
REVERIE_GRAPH_POLL_SECONDS / REVERIE_STATE_SECONDS / REVERIE_EVENT_POLL_SECONDS | 5 / 5 / 1 | How often the event stream re-reads the graph, host state and activation log |
NEO4J_* and REVERIE_EMBEDDINGS work exactly as in stdio mode.
| Endpoint | Auth | Returns |
|---|---|---|
GET /health (alias /brain/health) | none | { ok, neo4j, ts } |
POST /mcp | bearer | MCP Streamable HTTP (stateless; JSON responses). Any MCP client that speaks Streamable HTTP can use it |
GET /brain/graph?limit=400 | bearer | The most connected / most recent nodes (limit ≤ 1500), the relationships among them, totals per label and relationship type, and state |
GET /brain/state | bearer | dreaming, lastActivityAt, lastDreamAt, recent read/write counts, host CPU / load / memory, Presence usage and boost |
GET /brain/events?limit=400 | bearer | Server-Sent Events: the last 30 activation events, then state; afterwards every new activation as it happens, a graph diff (nodesAdded, nodesUpdated, nodesRemoved, relsAdded, relsRemoved, stats) whenever the graph changed, state every few seconds and error if a poll fails |
Node ids in the Brain API are the same numeric ids the tools return, as strings, so activation events and snapshot nodes line up. Archived memories are never shown. Embedding vectors never leave the server.
Activation log. Every search_memories (recall), create_memory / update_memory
(remember), create_connection / update_connection (connect), delete_* (forget) and real
dream run (dream.start / dream.end) appends one JSON line to REVERIE_EVENTS_PATH, whichever
transport served it. It holds ids, names and search terms only, is private to the user (mode 0600), is capped at
5 MB / 5,000 lines, and is what makes the Brain view light up. The graph itself never records what was looked at.
Behind Caddy on an agent's VM:
agent.example.com {
# … the agent's other routes …
handle /reverie/* {
uri strip_prefix /reverie
reverse_proxy 127.0.0.1:8643
}
}
and as a service:
[Unit]
Description=Reverie graph memory (HTTP mode)
After=network-online.target
[Service]
User=agent
EnvironmentFile=/home/agent/.config/reverie.env # NEO4J_*, REVERIE_SERVE_TOKEN, REVERIE_EMBEDDINGS…
ExecStart=/usr/bin/reverie serve
Restart=always
RestartSec=3
[Install]
WantedBy=multi-user.target
Give the portal https://<agent host>/reverie as the agent's brainUrl and the same token as
REVERIE_TOKEN.
MIT
FAQs
Reverie — graph memory that dreams. Graph-based memory system for AI agents. Store people, places, organizations as nodes. Build semantic relationships (KNOWS, WORKS_AT, CREATED). Hybrid keyword + semantic search finds partial matches and close variants.
The npm package @knowall-ai/reverie receives a total of 89 weekly downloads. As such, @knowall-ai/reverie popularity was classified as not popular.
We found that @knowall-ai/reverie demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.

Security News
It has been one year since Shai-Hulud made its first appearance on npm.

Research
/Security News
Operators behind PolinRider used a compromised GitHub account to plant malware in four development versions of a Packagist package with 700,000+ downloads.

Security News
GitHub Actions now supports cache-mode, a least-privilege control on the Actions cache aimed at the cache poisoning technique behind recent compromises.