Skip to content

Instantly share code, notes, and snippets.

@donbr
Last active December 18, 2025 07:43
Show Gist options
  • Select an option

  • Save donbr/5f831c3b3cb1bb29e79131eb569640f2 to your computer and use it in GitHub Desktop.

Select an option

Save donbr/5f831c3b3cb1bb29e79131eb569640f2 to your computer and use it in GitHub Desktop.
Multi-Agent Graph Coordination Architecture

Multi-Agent Graph Coordination Architecture

Problem Statement

Multiple AI agents (Claude Desktop, Claude Code, future agents) share write access to a Graphiti knowledge graph without coordinated protocols. This has resulted in:

  • Namespace pollution: 97 episodes in graphiti_meta_knowledge vs intended 8-10.
  • Duplicate episodes: Lack of idempotency checks.
  • Inconsistent content categorization: Different agents, different rules.
  • Loss of namespace semantic integrity.

1. Architecture Trade-Off Analysis Matrix (ATOM)

Criterion Single Writer Orchestrator Uniform MCP Guardrails Event Sourcing + Write-Ahead Log Namespace Ownership Model
Coordination Complexity Low (single point of control) Medium (distributed enforcement) High (log reconciliation) Medium (ownership registry)
Implementation Effort Medium (new orchestrator service) Low (extend existing MCP) High (new infrastructure) Low (config + conventions)
Latency Impact +50-100ms (hop through orchestrator) +10ms (validation checks) +20-50ms (log writes) None (static routing)
Fault Tolerance Low (single point of failure) High (distributed) Very High (replay capability) Medium (degraded if registry unavailable)
Multi-Agent Scalability Limited (bottleneck) High (horizontal) Very High (append-only) High (partitioned)
Existing Agent Compatibility Low (requires refactor) High (transparent injection) Low (API changes) Medium (config changes)
Auditability High (central logs) Medium (distributed logs) Very High (complete history) Medium (ownership records)
Recovery from Corruption Manual cleanup Manual cleanup Replay from checkpoint Scoped to namespace

Recommendation

Hybrid approach: Namespace Ownership Model + Uniform MCP Guardrails

  • Short-term: Define namespace ownership and inject guardrails via MCP.
  • Medium-term: Build orchestrator for high-coordination namespaces.
  • Long-term: Event sourcing for audit and recovery requirements.

2. Architecture Decision Record (ADR)

ADR-001: Multi-Agent Graph Write Coordination

  • Status: Proposed

Context: The Graphiti knowledge graph serves as shared long-term memory for multiple AI agents. Without coordination:

  • Claude Desktop wrote 89 episodes to graphiti_meta_knowledge without namespace-appropriate filtering.
  • Claude Code, after /verify guardrails, shows dramatically reduced pollution.
  • No mechanism exists to enforce "Robert's Rules" across heterogeneous agents.

Decision: Implement a Namespace Registry + MCP Guardrail Injection pattern:

  1. Namespace Registry (docs/NAMESPACE_REGISTRY.yaml):
  • Each namespace has: purpose, allowed_content_types, size_limit, owner_agent.
  • Owner has write access; others read-only or require explicit delegation.
  1. Guardrail MCP Server (for Claude Desktop and other agents):
  • Wraps graphiti-local MCP.
  • Injects /verify equivalent before allowing writes.
  • Validates namespace appropriateness before add_memory.
  1. Write Delegation Protocol:
  • Non-owner agents submit write requests to a queue.
  • Owner agent (or human) approves/routes to correct namespace.

Alternatives Considered:

Alternative Rejected Because
Single centralized orchestrator Single point of failure; latency; requires all agents to refactor.
Optimistic locking per episode Graphiti doesn't support CAS operations; race conditions remain.
Full event sourcing Over-engineered for current scale; high implementation cost.
Honor system (documentation only) Already failed; Claude Desktop ignored conventions.

Consequences:

  • Positive:

    • Backward compatible (existing agents continue working).
    • Namespace integrity enforced at protocol level.
    • Clear ownership enables accountability.
    • MCP guardrails reusable across agent types.
  • Negative:

    • Requires maintaining namespace registry.
    • MCP wrapper adds ~10ms latency per write.
    • Cross-namespace queries unaffected (coordination is write-side only).
  • Risks:

    • Registry drift if not kept synchronized.
    • Performance degradation under high write volume.

3. System Decomposition

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                             AGENT LAYER                                     β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚   Claude Code       β”‚   Claude Desktop    β”‚   Future Agents                 β”‚
β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”               β”‚
β”‚   β”‚ /verify     β”‚   β”‚   β”‚             β”‚   β”‚   β”‚             β”‚               β”‚
β”‚   β”‚ /health     β”‚   β”‚   β”‚ (no native  β”‚   β”‚   β”‚ (unknown    β”‚               β”‚
β”‚   β”‚ /graph-statsβ”‚   β”‚   β”‚  guardrails)β”‚   β”‚   β”‚  capabilities)              β”‚
β”‚   β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜   β”‚   β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜   β”‚   β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜               β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
           β”‚                     β”‚                     β”‚
           β–Ό                     β–Ό                     β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                          COORDINATION LAYER                                 β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”‚
β”‚  β”‚                     Guardrail MCP Server                            β”‚    β”‚
β”‚  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”               β”‚    β”‚
β”‚  β”‚  β”‚ Namespace    β”‚  β”‚ Pre-Write    β”‚  β”‚ Write        β”‚               β”‚    β”‚
β”‚  β”‚  β”‚ Registry     β”‚  β”‚ Validation   β”‚  β”‚ Delegation   β”‚               β”‚    β”‚
β”‚  β”‚  β”‚ Lookup       β”‚  β”‚ (purpose,    β”‚  β”‚ Queue        β”‚               β”‚    β”‚
β”‚  β”‚  β”‚              β”‚  β”‚  size, type) β”‚  β”‚              β”‚               β”‚    β”‚
β”‚  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜               β”‚    β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β”‚
β”‚                                    β”‚                                        β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”‚
β”‚  β”‚                     Namespace Registry                              β”‚    β”‚
β”‚  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚    β”‚
β”‚  β”‚  β”‚ graphiti_meta_knowledge:                                       β”‚ β”‚    β”‚
β”‚  β”‚  β”‚   purpose: "Getting started guide for new Claude instances"    β”‚ β”‚    β”‚
β”‚  β”‚  β”‚   owner: human_curated                                         β”‚ β”‚    β”‚
β”‚  β”‚  β”‚   allowed_types: [Getting Started, Core Lesson]                β”‚ β”‚    β”‚
β”‚  β”‚  β”‚   size_limit: 15                                               β”‚ β”‚    β”‚
β”‚  β”‚  β”‚   write_policy: explicit_approval_required                     β”‚ β”‚    β”‚
β”‚  β”‚  β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚    β”‚
β”‚  β”‚  β”‚ technical_patterns:                                            β”‚ β”‚    β”‚
β”‚  β”‚  β”‚   purpose: "Architecture patterns and technical learnings"     β”‚ β”‚    β”‚
β”‚  β”‚  β”‚   owner: any_agent                                             β”‚ β”‚    β”‚
β”‚  β”‚  β”‚   allowed_types: [Pattern, Lesson, Analysis, Anti-Pattern]     β”‚ β”‚    β”‚
β”‚  β”‚  β”‚   size_limit: 100                                              β”‚ β”‚    β”‚
β”‚  β”‚  β”‚   write_policy: self_service                                   β”‚ β”‚    β”‚
β”‚  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚    β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                     β”‚
                                     β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                            STORAGE LAYER                                    β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”‚
β”‚  β”‚                     graphiti-local MCP                              β”‚    β”‚
β”‚  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”               β”‚    β”‚
β”‚  β”‚  β”‚ add_memory   β”‚  β”‚ search_nodes β”‚  β”‚ get_episodes β”‚               β”‚    β”‚
β”‚  β”‚  β”‚              β”‚  β”‚              β”‚  β”‚              β”‚               β”‚    β”‚
β”‚  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜               β”‚    β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β”‚
β”‚                                    β”‚                                        β”‚
β”‚                                    β–Ό                                        β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”‚
β”‚  β”‚                     Neo4j Aura (Graph Storage)                      β”‚    β”‚
β”‚  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚    β”‚
β”‚  β”‚  β”‚ :Episodic nodes    β”‚ :Entity nodes    β”‚ :RELATES_TO edges    β”‚   β”‚    β”‚
β”‚  β”‚  β”‚ (group_id indexed) β”‚ (embeddings)     β”‚ (temporal validity)  β”‚   β”‚    β”‚
β”‚  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚    β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜


4. Integration Strategies

4.1 Guardrail MCP Server Implementation

# guardrail_mcp/server.py
from fastmcp import FastMCP
from pydantic import BaseModel
import yaml

mcp = FastMCP("graphiti-guardrail")

# Load namespace registry
with open("docs/NAMESPACE_REGISTRY.yaml") as f:
    NAMESPACE_REGISTRY = yaml.safe_load(f)

class WriteRequest(BaseModel):
    name: str
    episode_body: str
    group_id: str
    source: str = "text"
    source_description: str = ""

@mcp.tool()
async def guarded_add_memory(request: WriteRequest) -> dict:
    """Add memory with namespace validation and guardrails."""

    # 1. Lookup namespace config
    ns_config = NAMESPACE_REGISTRY.get(request.group_id)

    if ns_config is None:
        return {
            "status": "error",
            "message": f"Unknown namespace: {request.group_id}. "
                       f"Available: {list(NAMESPACE_REGISTRY.keys())}"
        }

    # 2. Check write policy
    if ns_config["write_policy"] == "explicit_approval_required":
        return {
            "status": "blocked",
            "message": f"Namespace '{request.group_id}' requires explicit approval. "
                       f"Purpose: {ns_config['purpose']}. "
                       f"Consider: {ns_config.get('suggested_alternative', 'technical_patterns')}"
        }

    # 3. Validate content type (heuristic based on episode name prefix)
    allowed_types = ns_config.get("allowed_types", [])
    episode_type = request.name.split(":")[0] if ":" in request.name else "Unknown"

    if allowed_types and episode_type not in allowed_types:
        return {
            "status": "warning",
            "message": f"Episode type '{episode_type}' not in allowed types {allowed_types}. "
                       f"Proceeding with advisory.",
            "proceed": True
        }

    # 4. Check size limit
    current_count = await get_namespace_episode_count(request.group_id)
    if current_count >= ns_config.get("size_limit", float("inf")):
        return {
            "status": "blocked",
            "message": f"Namespace '{request.group_id}' at capacity ({current_count}/{ns_config['size_limit']}). "
                       f"Consider cleanup or alternative namespace."
        }

    # 5. Delegate to underlying graphiti-local
    result = await graphiti_add_memory(
        name=request.name,
        episode_body=request.episode_body,
        group_id=request.group_id,
        source=request.source,
        source_description=request.source_description
    )

    return {"status": "success", "result": result}

4.2 Namespace Registry Schema

# docs/NAMESPACE_REGISTRY.yaml
namespaces:
  graphiti_meta_knowledge:
    purpose: "Lightweight getting started guide for new Claude instances"
    owner: human_curated
    allowed_types:
      - "Getting Started"
      - "Core Lesson"
      - "Procedure"
    size_limit: 15
    write_policy: explicit_approval_required
    suggested_alternative: technical_patterns

  graphiti_reference_docs:
    purpose: "Official Graphiti documentation and API reference"
    owner: human_curated
    allowed_types:
      - "Documentation"
      - "Code Example"
      - "API Reference"
    size_limit: 50
    write_policy: explicit_approval_required

  technical_patterns:
    purpose: "Architecture patterns, lessons, and technical learnings"
    owner: any_agent
    allowed_types:
      - "Pattern"
      - "Lesson"
      - "Analysis"
      - "Anti-Pattern"
      - "Best Practice"
    size_limit: 100
    write_policy: self_service

  session_reflections:
    purpose: "Cross-session learnings and agent reflections"
    owner: any_agent
    allowed_types:
      - "Reflection"
      - "Lesson"
      - "Insight"
    size_limit: 200
    write_policy: self_service

  # Dynamic project namespaces follow pattern:
  # {project_name}_{domain}:
  #   purpose: "Project-specific knowledge"
  #   owner: project_agent
  #   write_policy: self_service

4.3 LangGraph Supervisor Integration Pattern

For complex multi-agent workflows, integrate with LangGraph supervisor:

from langgraph_supervisor import create_supervisor
from langgraph.prebuilt import create_react_agent

# Reader agents - can query graph, cannot write
reader_agent = create_react_agent(
    model=model,
    tools=[search_nodes, search_memory_facts, get_episodes],
    name="graph_reader",
    prompt="You can search and retrieve from the knowledge graph. Do not attempt writes."
)

# Writer agent - single writer with validation
writer_agent = create_react_agent(
    model=model,
    tools=[guarded_add_memory],  # Uses guardrail MCP
    name="graph_writer",
    prompt=(
        "You manage writes to the knowledge graph. "
        "Always validate namespace appropriateness before writing. "
        "Route to correct namespace based on content type."
    )
)

# Supervisor orchestrates access
graph_supervisor = create_supervisor(
    agents=[reader_agent, writer_agent],
    model=model,
    prompt=(
        "You coordinate knowledge graph access. "
        "For queries, use graph_reader. "
        "For writes, use graph_writer who will validate namespace."
    )
)

5. Anti-Patterns / When NOT to Use This Approach

5.1 Anti-Pattern: Namespace per Agent

❌ DON'T: Create separate namespaces for each agent instance

claude_desktop_memories/
claude_code_session_123/
claude_code_session_456/

βœ… DO: Use semantic namespaces based on content domain

technical_patterns/
project_architectures/
career_opportunities/

Why: Agent-based namespacing defeats knowledge sharing and creates silos.

5.2 Anti-Pattern: Implicit Namespace Selection

# ❌ DON'T: Let agents guess namespace
await add_memory(
    name="Important Lesson",
    episode_body="...",
    group_id="graphiti_meta_knowledge"  # Agent guessed this
)

# βœ… DO: Explicit namespace validation with guardrails
result = await guarded_add_memory(
    name="Lesson: Important Discovery",
    episode_body="...",
    group_id="technical_patterns"  # Validated against registry
)
if result["status"] == "blocked":
    # Handle redirection

5.3 Anti-Pattern: Write-First, Organize-Later

❌ DON'T: "Just put it in meta_knowledge, we'll organize later"

  • Result: 97 episodes, most misplaced

βœ… DO: Validate before write, route to correct namespace

5.4 When NOT to Use Multi-Agent Graph Access

Scenario Recommendation
Single agent, single user Skip coordination layer; direct access sufficient
High-frequency writes (>100/min) Consider write-ahead log + batch processing
Strict consistency requirements Use database transactions, not MCP coordination
Cross-namespace queries dominant Focus on read optimization, not write coordination

6. Pseudocode / Coding Patterns

6.1 Pre-Write Validation Pattern

async def validate_before_write(
    episode_name: str,
    target_namespace: str,
    registry: dict
) -> tuple[bool, str, str | None]:
    """
    Returns: (is_valid, message, suggested_namespace)
    """
    ns_config = registry.get(target_namespace)

    if not ns_config:
        # Unknown namespace - suggest closest match
        suggested = find_closest_namespace(target_namespace, registry)
        return (False, f"Unknown namespace", suggested)

    # Extract episode type from naming convention "Type: Topic - Aspect"
    episode_type = episode_name.split(":")[0].strip() if ":" in episode_name else None

    if not episode_type:
        return (False, "Episode name must follow 'Type: Topic - Aspect' convention", None)

    allowed = ns_config.get("allowed_types", [])
    if allowed and episode_type not in allowed:
        # Find namespace that accepts this type
        suggested = find_namespace_for_type(episode_type, registry)
        return (False, f"Type '{episode_type}' not allowed in '{target_namespace}'", suggested)

    # Check capacity
    current_count = await get_episode_count(target_namespace)
    limit = ns_config.get("size_limit", float("inf"))

    if current_count >= limit:
        return (False, f"Namespace at capacity ({current_count}/{limit})", None)

    return (True, "Validation passed", None)

6.2 Observe-Before-Act Pattern (Claude Code Slash Commands)

# .claude/commands/write-episode.md
"""
Before writing to the knowledge graph, execute the observe-before-act protocol.

## Step 1: Verify Environment
```python
status = mcp__graphiti-local__get_status()
if status["status"] != "ok":
    STOP - Environment not ready

Step 2: Check Target Namespace

episodes = mcp__neo4j-cypher__read_neo4j_cypher(
    query='''
    MATCH (e:Episodic)
    WHERE e.group_id = $namespace
    RETURN count(*) as count, 
           collect(e.name)[0..5] as recent_episodes
    ''',
    params={"namespace": TARGET_NAMESPACE}
)
# Review: Does this content belong with existing episodes?

Step 3: Validate Content Type

  • Episode name must match: "Type: Topic - Aspect"
  • Allowed types for namespace: {from registry}
  • If mismatch, suggest alternative namespace

Step 4: Execute Write (only if steps 1-3 pass)

mcp__graphiti-local__add_memory(
    name=EPISODE_NAME,
    episode_body=CONTENT,
    group_id=TARGET_NAMESPACE,
    source_description=SOURCE
)

Step 5: Verify Write

  • Wait 15-20 seconds for async processing
  • Confirm episode appears in get_episodes

"""

6.3 Namespace Ownership Enforcement

class NamespaceOwnership:
    """Enforces single-writer pattern at namespace level."""

    OWNERSHIP_MAP = {
        "graphiti_meta_knowledge": "human",
        "graphiti_reference_docs": "human",
        "technical_patterns": "any",
        "session_reflections": "any",
        # Project namespaces owned by creating agent
    }

    @classmethod
    def can_write(cls, agent_id: str, namespace: str) -> bool:
        owner = cls.OWNERSHIP_MAP.get(namespace, "any")

        if owner == "any":
            return True
        if owner == "human":
            return agent_id == "human_operator"
        if owner == agent_id:
            return True

        return False

    @classmethod
    def delegate_write(cls, from_agent: str, to_agent: str, namespace: str) -> bool:
        """Explicit delegation of write permission."""
        # Implementation: Add to delegation table with TTL
        pass

Summary

Component Implementation Priority
Namespace Registry docs/NAMESPACE_REGISTRY.yaml P0 - Immediate
Agent Protocols docs/AGENT_GRAPH_PROTOCOLS.md P0 - Immediate
Guardrail MCP guardrail_mcp/server.py P1 - Short-term
Namespace Cleanup AGE-38 through AGE-45 P1 - Short-term
LangGraph Supervisor Integration Optional pattern P2 - Medium-term
Event Sourcing + Replay Future enhancement P3 - Long-term

Key Insight: The /verify, /health, /graph-stats commands already solved the problem for Claude Code by establishing observe-before-act as protocol. The remaining work is extending this pattern to all agents via MCP guardrails and formalizing namespace ownership.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment