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¶
# Search all groups (no filtering)
results = await client.search(
query="architecture",
group_ids=None # None = search all groups
)
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
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 Commands - Command reference
- FEAT-GR-PRE-001 Design - Technical design
- 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