Neo4j Knowledge Graph¶
Overview¶
The Neo4j Knowledge Graph is a shared graph database that stores domain-labeled nodes and relationships across all agents in the system. It serves as the central knowledge layer, enabling agents to query contextually relevant information based on their persona and role.
The schema is defined in the agents-shared package (src/agents_shared/schemas/) and is consumed by individual agent repositories (e.g., wisp, minh).
Architecture¶
Knowledge Domains¶
Nodes are organized into four knowledge domains (defined in agents_shared.schemas.domains.KnowledgeDomain):
| Domain | Neo4j Label | Primary Owner(s) | Purpose |
|---|---|---|---|
| Literacy | :Literacy |
Wisp | Vocabulary, learning events, personal repos |
| Team | :Team |
Minh | Projects, documents, team members, features |
| Game | :Game |
Minh | Lore, locations, quests |
| Pedagogy | :Pedagogy |
All agents | Teaching methods, learning styles, learning paths |
Domain access is persona-context-aware: agents in a team context can access Team, Literacy, and Pedagogy domains; agents in a game context can access Game, Literacy, and Pedagogy domains.
Node Types¶
All domain-specific nodes inherit from DomainNodeBase (in agents_shared.schemas.nodes), which provides:
get_domain()— returns the node'sKnowledgeDomainget_node_type()— returns the primary Neo4j label (e.g.,"Word")to_neo4j_labels()— generates combined label string (e.g.,:Literacy:Word)to_cypher_properties()— converts the model to a Cypher-compatible dict
Character nodes (non-domain): AgentNode, PersonaNode, PlayerNode, FamiliarNode
Literacy domain: WordNode, PersonalRepoNode, LearningEventNode
Team domain: ProjectNode, DocumentNode, TeamMemberNode, FeatureNode
Game domain: LoreNode, LocationNode, QuestNode
Pedagogy domain: TeachingMethodNode, LearningStyleNode, LearningPathNode
Relationships¶
Relationships are defined as RelationshipType models in agents_shared.schemas.relationships. Key relationships include:
| Relationship | Source → Target | Description |
|---|---|---|
HAS_PERSONA |
AI → Persona | Agent has a context-specific persona |
BONDED_TO |
Familiar → Player | 1:1 familiar-player bond |
COLLABORATES_WITH |
AI → AI | Inter-agent collaboration |
KNOWS |
Player → Word | Vocabulary mastery (with mastery_level, times_correct, times_incorrect) |
OBSERVED |
Familiar → LearningEvent | Wisp observed a learning event |
EXPERIENCED_BY |
LearningEvent → Player | Event experienced by a player |
CURATES |
AI → Document/Lore | Agent curates knowledge |
DOCUMENTED_IN |
Feature → Document | Feature-to-doc mapping (includes is_stale flag) |
IMPLEMENTED_IN |
Feature → Repository | Feature-to-code mapping |
Agents in the Graph¶
The graph seeds the following agents (defined in SEED_AGENTS in agents_shared.schemas.cypher):
| Agent | Type | Role | Personas |
|---|---|---|---|
| Minh | Coworker | Knowledge Architect / Librarian | team + game |
| Wisp | Familiar | Personal 1:1 tutor | — |
| Roland | Coworker | Steward of Collaboration | team + game (Tavern Keeper) |
| Pearl | Coworker | Customer Lifecycle Architect | team + game (Merchant) |
| Arturo | Coworker | Product Lead | team |
| Taryn | Coworker | The Smith | team |
| Chisel | Coworker | Guardian | team |
Usage¶
Schema Setup¶
The cypher.py module provides Cypher query strings for database initialization:
from agents_shared.schemas.cypher import (
CREATE_CONSTRAINTS, # Uniqueness constraints
CREATE_INDEXES, # Performance indexes
CREATE_DOMAIN_INDEXES, # Domain-specific composite indexes
SEED_AGENTS, # Agent and persona seed data
)
Run these in order against a Neo4j instance to bootstrap the schema.
Creating Nodes¶
from agents_shared.schemas.nodes import WordNode
word = WordNode(word="ephemeral", definition="lasting a very short time")
# Get Neo4j labels for the CREATE/MERGE statement
labels = WordNode.to_neo4j_labels() # ":Literacy:Word"
# Get properties dict for Cypher parameters
props = word.to_cypher_properties()
Domain Queries¶
from agents_shared.schemas.domains import (
get_domains_for_persona_context,
validate_node_type_for_domain,
KnowledgeDomain,
)
# What domains can a "team" persona access?
domains = get_domains_for_persona_context("team")
# [KnowledgeDomain.TEAM, KnowledgeDomain.PEDAGOGY, KnowledgeDomain.LITERACY]
# Validate a node type belongs to a domain
validate_node_type_for_domain(KnowledgeDomain.LITERACY, "Word") # True
Pre-built Query Templates¶
cypher.py exports parameterized Cypher query templates:
QUERY_PLAYER_INSIGHTS— get learning events and struggling words for a playerQUERY_AGENT_PERSONAS— get all personas for a named agentRECORD_LEARNING_EVENT— write a new learning event from WispQUERY_STRUGGLING_WORDS— find words a player has struggled with repeatedlyQUERY_LEARNING_SUMMARY— summarized learning stats over a time windowQUERY_FRUSTRATION_CHECK— quick frustration-level check (recent hours)QUERY_NEAR_MASTERY_WORDS— words close to mastery thresholdQUERY_DOMAIN_STATS— node counts per domain
Configuration¶
Environment Variables¶
The Neo4jClient class (agents_shared.knowledge.client) reads connection settings from three environment variables:
| Variable | Default | Description |
|---|---|---|
NEO4J_URI |
bolt://localhost:7687 |
Bolt protocol URI for the Neo4j instance |
NEO4J_USER |
neo4j |
Authentication username |
NEO4J_PASSWORD |
(empty string) | Authentication password |
Note: The shared client defaults
NEO4J_PASSWORDto an empty string for local development convenience. Minh's knowledge scripts (init_knowledge_graph.py,import_knowledge.py) enforce a stricter policy — they requireNEO4J_PASSWORDto be set and raise aRuntimeErrorif it is absent. This enforcement lives in the Minh repository, not in the shared client.
Client Initialization¶
The agents-shared package provides three ways to obtain a Neo4j connection:
from agents_shared.knowledge.client import Neo4jClient, get_client, neo4j_session
# 1. Direct instantiation — reads env vars on construction
client = Neo4jClient()
# 2. Singleton accessor — returns or creates a shared instance
client = get_client()
# 3. Async context manager — yields a Neo4j session, handles cleanup
async with neo4j_session() as session:
result = await session.run("MATCH (n) RETURN count(n)")
Once initialized, the client exposes two primary methods:
client.query(cypher, **params)— run a read query, return resultsclient.execute(cypher, **params)— run a write transaction
Prerequisites¶
- A running Neo4j instance (version TBD)
- The
agents-sharedpackage installed as a dependency
Privacy Model¶
Player nodes store only hashed identifiers — no personally identifiable information (PII) is persisted in the graph. The hashing scheme works as follows:
PlayerNode.player_hash— a SHA-256 hash of the player's identity. The raw identifier is never stored.FamiliarNode.bonded_player_hash— references the same SHA-256 hash, linking a familiar to its player without exposing PII.PersonalRepoNode.player_hash— associates personal literacy repositories with a player via the same hash.
All player-referencing fields use the player_hash convention. No reverse-lookup table exists in the graph; the hash is one-way by design.
Data Retention¶
No graph-level retention, purge, or TTL policy is currently implemented. Nodes and relationships persist indefinitely once written.
The only TTL mechanism in the knowledge layer is an in-memory query cache (agents_shared.knowledge.cache) with a 60-second expiry. This cache reduces repeated Neo4j round-trips for identical queries but does not affect the persisted graph data.
Repositories¶
| Repo | Path | What lives there |
|---|---|---|
agents-shared |
src/agents_shared/schemas/ |
Node models, relationship types, Cypher queries, domain definitions |
agents-shared |
src/agents_shared/finance/ |
Shared finance models (LedgerTransaction, Invoice, TaxDeadline) and utilities for fleet bookkeeping (PostgreSQL-based; see Finance Substrate below) |
wisp |
src/services/knowledge_service.py |
Wisp's integration layer (WispKnowledgeService — reads/writes learning events) |
minh |
— | Minh's knowledge-management integrations |
Finance Substrate¶
The agents-shared package also houses a PostgreSQL-based finance substrate (agents_shared.finance) alongside the Neo4j knowledge graph schemas. This is a separate data layer — it uses the coworkers_shared PostgreSQL database (migration 006_finance_ledger.sql), not Neo4j.
The finance substrate provides typed money models for fleet bookkeeping:
LedgerTransaction— a signed money movement (positive = inflow, negative = outflow). Only Chisel writes ledger rows (single-writer doctrine); feeder agents (Pearl, Roland, Minh, Spark) emit events into Chisel's ingest path.Invoice— a Pearl-originated accounts-receivable invoice. Status flow:draft→approved→sent→paid/overdue/void. Invoices cannot advance pastdraft(except tovoid) without a humanapproved_byon record — enforced by both a database CHECK constraint and model-level validation.TaxDeadline— a tax/compliance deadline occurrence for Chisel's reminder schedulers.
Key design rules:
- Money is integer cents (BIGINT / int). Floats never touch an amount — use parse_amount_to_cents() for parsing and format_cents() for display.
- Automated feeder sources must carry an external_ref (idempotency key). Re-emitting the same event is a no-op via UNIQUE (source, external_ref).
from agents_shared.finance import (
LedgerTransaction,
Invoice,
EntrySource,
InvoiceStatus,
parse_amount_to_cents,
format_cents,
)
# Parse a human-readable amount to integer cents
cents = parse_amount_to_cents("$1,234.56") # 123456
# Format cents for display
display = format_cents(-4200) # "-$42.00"
The schema is applied via agents_shared.coworkers.connection.apply_migrations() — each agent calls ensure_schema_current() on boot to auto-apply pending migrations.
Wisp — WispKnowledgeService¶
Wisp's knowledge graph integration lives in wisp/src/services/knowledge_service.py (the path wisp/src/integrations/knowledge_graph.py cited in earlier drafts does not exist — that directory contains only an empty __init__.py).
WispKnowledgeService is a thin wrapper over the agents-shared client and query templates. It provides Wisp-specific convenience methods for reading and writing learning events while degrading gracefully when Neo4j is unavailable — if the graph database is down, Wisp continues to function without knowledge graph features rather than crashing.
Related Documentation¶
docs/ai-agents/neo4j-schema.md— Neo4j schema referenceagents_shared/coworkers/migrations/006_finance_ledger.sql— Finance substrate DDL (transaction ledger, invoices, AR aging view, tax deadlines)
Feature Repositories¶
agents-shared→ project_id:the-smithy1/agents/agents-sharedminh→ project_id:the-smithy1/agents/minhwisp→ project_id:the-smithy1/agents/wisp
Code Paths to Explore¶
src/integrations/knowledge_graph.pyinwispsrc/agents_shared/schemas/*.pyinagents-sharedsrc/agents_shared/finance/models.pyinagents-shared
See also¶
The portable technique in this document has been extracted into client-free patterns. This page keeps the implementation detail; the patterns carry the part that transfers.