Graphiti Project Namespaces¶
What are Project Namespaces?
Project namespaces enable multiple projects to share a single Graphiti instance while maintaining complete knowledge isolation. Each project gets its own prefixed knowledge groups, preventing cross-contamination while allowing shared system-level knowledge.
Table of Contents¶
- The Problem It Solves
- Quick Start
- Core Concepts
- Configuration
- Working with Namespaces
- Best Practices
- FAQ
- See Also
The Problem It Solves¶
Multi-Project Knowledge Contamination¶
Without namespacing, multiple projects sharing a Graphiti instance face knowledge collision:
Project A: Adds architecture decision about authentication
↓
Project B: Searches for "authentication"
↓
Project B: Incorrectly retrieves Project A's architecture
↓
Project B: Implements wrong authentication approach
Real-World Example¶
Scenario: GuardKit development repository contains multiple features being developed in parallel using Conductor:
| Workspace | Project | Risk Without Namespacing |
|---|---|---|
feat-auth-impl |
Authentication feature | Shares knowledge with billing feature |
feat-billing |
Billing feature | Contaminates auth decisions |
feat-admin-ui |
Admin UI | Retrieves mixed context from both |
Impact: - Feature-specific decisions leak between projects - Parallel development corrupts each other's knowledge - Context becomes increasingly noisy and unreliable
What Namespacing Provides¶
With Project Namespaces:
Project A:
- Group: "project-a__architecture"
- Knowledge: Authentication decisions isolated to project-a
Project B:
- Group: "project-b__architecture"
- Knowledge: Billing decisions isolated to project-b
System:
- Group: "role_constraints" (no prefix)
- Knowledge: Shared quality gates, templates
Result: Perfect isolation with shared system knowledge
Quick Start¶
1. Auto-Detection (Recommended)¶
from guardkit.knowledge.graphiti_client import init_graphiti, get_graphiti
# Initialize with auto-detection from directory name
await init_graphiti() # Detects project ID from current directory
# Get client
client = get_graphiti()
# Add project-specific knowledge
await client.add_episode(
name="Authentication Decision",
episode_body="Using JWT with refresh tokens",
group_id="project_architecture" # Automatically prefixed
)
How it works:
- Current directory: /home/user/my-awesome-app
- Auto-detected project_id: my-awesome-app
- Group becomes: my-awesome-app__project_architecture
2. Explicit Configuration¶
Create .guardkit/graphiti.yaml:
# .guardkit/graphiti.yaml
enabled: true
neo4j_uri: bolt://localhost:7687
neo4j_user: neo4j
neo4j_password: password123
project_id: my-custom-project-id # Override auto-detection
3. Environment Variable Override¶
# Highest priority - overrides everything
export GUARDKIT_PROJECT_ID=production-deployment
# Now all projects use this ID
python your_script.py
Core Concepts¶
Project ID Format¶
Normalization Rules: - Lowercase only - Spaces → hyphens - Non-alphanumeric (except hyphens) removed - Max 50 characters - Alphanumeric + hyphens only
Examples:
"My Project" → "my-project"
"Project v2.0!" → "project-v20"
"Super_Long_Project_Name_123" → "super-long-project-name-123"
"a" * 100 → "a" * 50 # Truncated
Group ID Prefixing¶
Project Groups (Auto-Prefixed)¶
Standard project-specific groups:
PROJECT_GROUP_NAMES = [
"project_overview",
"project_architecture",
"feature_specs",
"project_decisions",
"project_constraints",
"domain_knowledge",
]
Prefixing behavior:
project_id = "my-app"
# Project group
client.get_group_id("project_overview")
# Result: "my-app__project_overview"
# Custom project group (defaults to project scope)
client.get_group_id("custom_knowledge")
# Result: "my-app__custom_knowledge"
System Groups (Never Prefixed)¶
Shared across all projects:
SYSTEM_GROUP_IDS = [
"role_constraints",
"quality_gate_configs",
"implementation_modes",
"guardkit_templates",
"guardkit_patterns",
]
No prefixing:
project_id = "my-app"
# System group
client.get_group_id("role_constraints", scope="system")
# Result: "role_constraints" # No prefix
# Any guardkit_* group
client.get_group_id("guardkit_custom")
# Result: "guardkit_custom" # Auto-detected as system
Configuration Priority¶
Order of precedence (highest to lowest):
- Explicit parameter to
GraphitiClient - Environment variable:
GUARDKIT_PROJECT_ID - YAML config:
.guardkit/graphiti.yaml - Auto-detection: Current directory name
Example:
# Priority 1: Explicit parameter (highest)
config = GraphitiConfig(project_id="explicit-id")
client = GraphitiClient(config)
# Result: "explicit-id"
# Priority 2: Environment variable
os.environ["GUARDKIT_PROJECT_ID"] = "env-id"
settings = load_graphiti_config()
# Result: "env-id"
# Priority 3: YAML config
# .guardkit/graphiti.yaml contains: project_id: yaml-id
settings = load_graphiti_config()
# Result: "yaml-id"
# Priority 4: Auto-detection (fallback)
# Current directory: /home/user/my-project
client.get_project_id(auto_detect=True)
# Result: "my-project"
Configuration¶
Basic Configuration¶
Auto-Detection (Zero Config)¶
from guardkit.knowledge.graphiti_client import init_graphiti, get_graphiti
# Uses current directory name as project_id
await init_graphiti()
client = get_graphiti()
print(client.project_id) # e.g., "guardkit"
YAML Configuration¶
# .guardkit/graphiti.yaml
enabled: true
neo4j_uri: bolt://localhost:7687
neo4j_user: neo4j
neo4j_password: password123
project_id: my-project-name # Optional: override auto-detection
Programmatic Configuration¶
from guardkit.knowledge.graphiti_client import GraphitiConfig, GraphitiClient
config = GraphitiConfig(
enabled=True,
neo4j_uri="bolt://localhost:7687",
neo4j_user="neo4j",
neo4j_password="password123",
project_id="my-app" # Explicit project ID
)
client = GraphitiClient(config)
await client.initialize()
Advanced Configuration¶
Multi-Project Setup¶
For development environments with multiple projects:
# Project A
config_a = GraphitiConfig(project_id="project-a")
client_a = GraphitiClient(config_a)
await client_a.initialize()
# Project B
config_b = GraphitiConfig(project_id="project-b")
client_b = GraphitiClient(config_b)
await client_b.initialize()
# Both clients share same Neo4j instance
# Knowledge is isolated by project_id prefix
Disabling Auto-Detection¶
# Disable auto-detection (use system scope)
config = GraphitiConfig(project_id=None)
client = GraphitiClient(config, auto_detect_project=False)
# This client operates in system scope only
# All groups must be explicitly system groups
Working with Namespaces¶
Adding Knowledge¶
Project-Specific Knowledge¶
# Automatically prefixed
await client.add_episode(
name="Feature Spec",
episode_body="User authentication using OAuth2",
group_id="feature_specs" # → "my-project__feature_specs"
)
# Explicit project scope
await client.add_episode(
name="Custom Knowledge",
episode_body="Domain-specific rules",
group_id="custom_group",
scope="project" # → "my-project__custom_group"
)
System-Level Knowledge¶
# System group (no prefix)
await client.add_episode(
name="Quality Gate",
episode_body="Minimum 80% test coverage required",
group_id="quality_gate_configs" # → "quality_gate_configs"
)
# Explicit system scope
await client.add_episode(
name="Template",
episode_body="React component pattern",
group_id="custom_template",
scope="system" # → "custom_template"
)
Searching Knowledge¶
Project-Only Search¶
# Search current project only
results = await client.search(
query="authentication",
group_ids=["project_architecture", "feature_specs"]
# → ["my-project__project_architecture", "my-project__feature_specs"]
)
System-Only Search¶
# Search system knowledge only
results = await client.search(
query="quality gates",
group_ids=["quality_gate_configs", "guardkit_templates"]
# → ["quality_gate_configs", "guardkit_templates"] (no prefix)
)
Mixed Search¶
# Search both project and system knowledge
results = await client.search(
query="patterns",
group_ids=[
"project_architecture", # Project group (prefixed)
"guardkit_patterns" # System group (not prefixed)
]
# → ["my-project__project_architecture", "guardkit_patterns"]
)
Cross-Project Search¶
# Search specific other projects (explicit prefixes)
results = await client.search(
query="shared patterns",
group_ids=[
"project-a__architecture", # Explicit prefix
"project-b__architecture", # Explicit prefix
"guardkit_patterns" # System group
]
)
Global Search¶
FalkorDB limitation: On FalkorDB,
group_ids=Nonesearches only the default graph (typically empty), not all graphs. You must pass explicit group_ids to search across multiple groups. This differs from Neo4j wheregroup_ids=Nonesearches all groups.
# Neo4j: searches all groups
# FalkorDB: searches default graph only (returns 0 results)
results = await client.search(
query="architecture",
group_ids=None
)
# FalkorDB: explicitly list all groups to search
results = await client.search(
query="architecture",
group_ids=[
"project_architecture", # Project group (prefixed)
"architecture_decisions", # System group
"feature_build_architecture",
"guardkit_patterns",
]
)
Preventing Double-Prefixing¶
The client automatically detects and prevents double-prefixing:
project_id = "my-app"
# Already prefixed
await client.add_episode(
name="Episode",
episode_body="Content",
group_id="my-app__project_overview" # Already has prefix
)
# Result: "my-app__project_overview" (not "my-app__my-app__project_overview")
# Detection logic
def _is_already_prefixed(group_id: str) -> bool:
"""Check if group_id already has {project_id}__ prefix."""
return f"{self.project_id}__" in group_id
MCP Access and Group ID Namespacing¶
When accessing the knowledge graph via the Graphiti MCP server (Claude Code sessions), the group ID behaviour differs from the Python client.
The Key Difference¶
Python client auto-prefixes project groups:
# Python client — pass logical (unprefixed) name
client.search(group_ids=["project_architecture"])
# → internally searches: "guardkit__project_architecture"
MCP server is pass-through — no auto-prefix:
# MCP server — must pass fully qualified group ID
mcp__graphiti__search_nodes(group_ids=["guardkit__project_architecture"]) # Correct ✅
mcp__graphiti__search_nodes(group_ids=["project_architecture"]) # Wrong ❌ → returns nothing
Why This Difference Exists¶
The MCP server (graphiti-core) has no knowledge of your project's project_id beyond what
you configure in --group-id. It does not apply prefixes automatically. The Python client's
GraphitiClient.get_group_id() method is a GuardKit abstraction on top of graphiti-core
that handles prefixing transparently.
Using Both Access Methods Together¶
When a project uses both the Python client (CLI / AutoBuild) and the MCP server (Claude Code sessions), the same data is accessible from both — as long as group IDs are specified correctly:
| Access Method | How to Reference Project Groups |
|---|---|
| Python client | Logical name: project_architecture → auto-prefixed |
| MCP server | Full name: guardkit__project_architecture → explicit |
| System groups | Same in both: product_knowledge, command_workflows |
Group ID Reference for MCP Use¶
The .claude/rules/graphiti-knowledge-graph.md file in each project contains the complete
list of group IDs to use in MCP tool calls. This file is loaded automatically by Claude Code
and instructs it to always pass explicit group_ids with the correct prefix.
If you add a new project group via the Python client (e.g., task_outcomes), it will be
stored as {project_id}__task_outcomes in FalkorDB. To search it via MCP, pass that full
name: guardkit__task_outcomes.
Common Mistake: Searching Without group_ids¶
Both the Python client and MCP server require explicit group_ids for FalkorDB searches.
Omitting group_ids returns no results (FalkorDB limitation — unlike Neo4j which searches
all groups):
# Wrong — returns 0 results on FalkorDB
mcp__graphiti__search_nodes(query="authentication patterns")
# Correct — pass all relevant group_ids
mcp__graphiti__search_nodes(
query="authentication patterns",
group_ids=["product_knowledge", "guardkit__project_architecture"]
)
Best Practices¶
1. Use Auto-Detection in Development¶
# Good: Let directory name drive project ID
await init_graphiti() # Uses current directory
# Avoid: Hardcoding project IDs in code
config = GraphitiConfig(project_id="hardcoded-project")
Rationale: Auto-detection works seamlessly with Conductor workspaces and multi-project setups.
2. Override with YAML for Deployment¶
Rationale: Deployment environments need stable, explicit IDs.
3. Use Environment Variables for Multi-Tenancy¶
# Tenant A
export GUARDKIT_PROJECT_ID=tenant-a
python app.py
# Tenant B
export GUARDKIT_PROJECT_ID=tenant-b
python app.py
Rationale: Same codebase, different namespaces.
4. Prefix Custom Groups Explicitly¶
# Good: Explicit scope for custom groups
await client.add_episode(
name="Custom",
episode_body="Data",
group_id="my_custom_group",
scope="project" # Explicit
)
# Okay: Relies on default (project scope)
await client.add_episode(
name="Custom",
episode_body="Data",
group_id="my_custom_group" # Defaults to project
)
5. Use System Groups for Templates¶
# Good: Templates are system-level
await client.add_episode(
name="React Component Template",
episode_body="Standard component structure",
group_id="guardkit_templates" # System group
)
# Bad: Templates in project scope
await client.add_episode(
name="React Component Template",
episode_body="Standard component structure",
group_id="project_templates" # Isolated to project
)
6. Search Narrowly¶
# Good: Specific groups
results = await client.search(
query="auth",
group_ids=["project_architecture"] # Narrow search
)
# Avoid: Global search (noisy results)
results = await client.search(
query="auth",
group_ids=None # All groups
)
FAQ¶
Q: What happens if I don't set a project_id?¶
A: Depends on configuration:
# Auto-detection enabled (default)
client = GraphitiClient(config)
# Uses current directory name as project_id
# Auto-detection disabled
client = GraphitiClient(config, auto_detect_project=False)
# project_id is None
# Can only use system groups
# Project groups will raise ValueError
Q: Can I change project_id after client creation?¶
A: No, GraphitiConfig is immutable (frozen dataclass):
config = GraphitiConfig(project_id="original")
config.project_id = "new" # ❌ Raises AttributeError
# Must create new config and client
new_config = GraphitiConfig(project_id="new")
new_client = GraphitiClient(new_config)
Q: How do I share knowledge between projects?¶
A: Use explicit cross-project group IDs or system groups:
# Option 1: System groups
await client.add_episode(
name="Shared Pattern",
episode_body="Common approach",
group_id="guardkit_patterns", # System group
scope="system"
)
# Option 2: Cross-project search
results = await client.search(
query="pattern",
group_ids=[
"project-a__architecture", # Project A
"project-b__architecture" # Project B
]
)
Q: What if two projects have the same directory name?¶
A: Use explicit configuration:
# Project A: .guardkit/graphiti.yaml
project_id: project-a-auth
# Project B: .guardkit/graphiti.yaml
project_id: project-b-auth
Or use environment variables:
# Workspace 1
cd workspace-1/auth
export GUARDKIT_PROJECT_ID=workspace-1-auth
# Workspace 2
cd workspace-2/auth
export GUARDKIT_PROJECT_ID=workspace-2-auth
Q: Can I use special characters in project_id?¶
A: Only alphanumeric and hyphens:
# Valid
GraphitiConfig(project_id="my-project-123") # ✅
# Invalid (raises ValueError)
GraphitiConfig(project_id="my@project") # ❌
GraphitiConfig(project_id="my_project") # ❌
GraphitiConfig(project_id="my.project") # ❌
Normalization auto-fixes auto-detected IDs:
Q: How do I list all groups for a project?¶
A: Query Neo4j directly or use client methods:
# Search all project groups
results = await client.search(
query="", # Empty query
group_ids=[
"project_overview",
"project_architecture",
"feature_specs",
"project_decisions",
"project_constraints",
"domain_knowledge"
]
)
# Or use Neo4j Cypher query (advanced)
# MATCH (e:Episode)
# WHERE e.group_id STARTS WITH "my-project__"
# RETURN DISTINCT e.group_id
Q: What's the performance impact of prefixing?¶
A: Negligible:
- Prefixing: Simple string concatenation (
O(1)) - Storage: ~10-20 extra bytes per group ID
- Indexing: Neo4j indexes on
group_idhandle prefixes efficiently - Search: No performance difference vs unprefixed groups
Benchmark (10,000 episodes):
Q: Can I migrate existing knowledge to namespaced groups?¶
A: Yes, use a migration script:
async def migrate_to_namespaces(client, old_group, new_project_id):
"""Migrate knowledge from unprefixed to prefixed groups."""
# Get all episodes in old group
episodes = await client.search(query="", group_ids=[old_group])
# Re-add with project prefix
for episode in episodes:
await client.add_episode(
name=episode.name,
episode_body=episode.content,
group_id=old_group, # Will be prefixed with new_project_id
project_id=new_project_id
)
# Optionally: Delete old unprefixed episodes
See Also¶
- Graphiti Integration Guide - Setup and core concepts
- Graphiti Claude Code Integration - MCP access, group IDs for Claude Code sessions
- Graphiti Shared Infrastructure - Multi-project FalkorDB setup
- Graphiti Commands - Command reference
- GraphitiClient API Reference - Implementation details
Implementation Reference¶
File Locations¶
- Client:
guardkit/knowledge/graphiti_client.py - Config:
guardkit/knowledge/config.py - Tests:
tests/knowledge/test_graphiti_client_project_id.py(54 tests)tests/knowledge/test_graphiti_group_prefixing.py(37 tests)
Key Functions¶
# Normalization
def normalize_project_id(name: str) -> str:
"""Normalize project ID to valid format."""
# Group ID prefixing
def get_group_id(self, group_name: str, scope: str = "project") -> str:
"""Get correctly prefixed group ID."""
# Project group detection
def is_project_group(self, group_name: str) -> bool:
"""Check if group should be prefixed."""
# Auto-detection
def get_current_project_name() -> str:
"""Get current directory name for auto-detection."""
Last Updated: 2026-01-31 Version: 1.0.0 Status: Production-ready