Skip to content

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

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

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):

  1. Explicit parameter to GraphitiClient
  2. Environment variable: GUARDKIT_PROJECT_ID
  3. YAML config: .guardkit/graphiti.yaml
  4. 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

# Search current project only
results = await client.search(
    query="authentication",
    group_ids=["project_architecture", "feature_specs"]
    # → ["my-project__project_architecture", "my-project__feature_specs"]
)
# 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)
)
# 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"]
)
# 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
    ]
)
# 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

# .guardkit/graphiti.yaml
project_id: production-auth-service

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:

# Directory: "my_project@v2.0"
# Normalized: "my-projectv20"

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_id handle prefixes efficiently
  • Search: No performance difference vs unprefixed groups

Benchmark (10,000 episodes):

Unprefixed: 1.23s
Prefixed:   1.24s (0.8% slower)

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


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