Graphiti Architecture Documentation¶
System Overview¶
Graphiti provides GuardKit with persistent memory across Claude Code sessions through a temporal knowledge graph. This integration prevents context-less decision making by ensuring sessions have access to architectural decisions, failure patterns, quality gates, and historical outcomes.
Architecture Diagram¶
┌─────────────────────────────────────────────────────────────────┐
│ Claude Code Session │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ GuardKit Command (/task-work, /feature-build, etc.) │ │
│ │ │ │
│ │ 1. Load Context (Phase 1) │ │
│ │ ↓ │ │
│ │ ┌─────────────────────────────────────────────────┐ │ │
│ │ │ Context Loader │ │ │
│ │ │ - load_critical_context() │ │ │
│ │ │ - load_feature_overview() │ │ │
│ │ │ - load_role_context() │ │ │
│ │ │ - load_critical_adrs() │ │ │
│ │ │ - load_failed_approaches() │ │ │
│ │ └─────────────────────────────────────────────────┘ │ │
│ │ ↓ │ │
│ │ ┌─────────────────────────────────────────────────┐ │ │
│ │ │ GraphitiClient │ │ │
│ │ │ - search(query, group_ids, num_results) │ │ │
│ │ │ - add_episode(name, body, group_id) │ │ │
│ │ └─────────────────────────────────────────────────┘ │ │
│ │ ↓ │ │
│ └───────────────────────────────────────────────────────────┘ │
│ ↓ │
└─────────────────────────────────────────────────────────────────┘
↓
HTTP/REST
↓
┌─────────────────────────────────────────────────────────────────┐
│ Graphiti API │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Entities │ │ Episodes │ │ Facts │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ ↓ ↓ ↓ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ OpenAI Embeddings (for search) │ │
│ └──────────────────────────────────────────────────┘ │
│ ↓ │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ Neo4j │
│ (Graph Database) │
└─────────────────────────────────────────────────────────────────┘
Data Flow:
1. Command starts → Context Loader queries Graphiti
2. Graphiti searches knowledge graph → Returns relevant context
3. Context formatted and injected into Claude Code session
4. Task executes with full knowledge of decisions, patterns, failures
5. On completion → Outcome/ADR/TurnState captured back to Graphiti
Knowledge Categories¶
Complete table of all group_ids used in Graphiti for organizing knowledge:
| Group ID | Contents | Seeded By | Typical Episodes | Context Loader |
|---|---|---|---|---|
product_knowledge |
GuardKit identity, core principles, workflow philosophy | TASK-GI-001 | ~15 | load_critical_context() |
command_workflows |
Command patterns, argument flows, state transitions | TASK-GI-001 | ~25 | load_critical_context() |
quality_gate_phases |
Phase definitions, thresholds, gate criteria | TASK-GI-001 | ~12 | load_critical_context() |
technology_stack |
Python, CLI, Claude Code, SDK, worktrees | TASK-GI-001 | ~10 | load_critical_context() |
feature_build_architecture |
Player-Coach delegation, task-work subprocess | TASK-GI-001 | ~20 | load_critical_context() (when command="feature-build") |
architecture_decisions |
Core ADRs (hash IDs, no task-work, subprocess isolation) | TASK-GI-001 + TASK-GE-007 | ~8 | load_critical_context() + load_critical_adrs() |
failure_patterns |
Known bugs, anti-patterns, fixes | TASK-GI-001 | ~10 | load_critical_context() |
component_status |
What's complete/incomplete/deprecated | TASK-GI-001 | ~5 | Not loaded (informational) |
integration_points |
How components connect | TASK-GI-001 | ~8 | Not loaded (informational) |
templates |
Template metadata for semantic search | TASK-GI-001 | ~5 | Not loaded (future feature) |
agents |
Agent capabilities and boundaries | TASK-GI-001 | ~15 | Not loaded (future feature) |
patterns |
Design pattern knowledge | TASK-GI-001 | ~10 | Not loaded (future feature) |
rules |
Rule applicability and code examples | TASK-GI-001 | ~12 | Not loaded (future feature) |
adrs |
Dynamic Architecture Decision Records | TASK-GI-004 | Variable | load_critical_adrs() |
task_outcomes |
Task completion episodes with outcomes | TASK-GI-005 | Variable | Not loaded (similarity search) |
feature_overviews |
Feature identity preservation | TASK-GE-001 | ~1 per feature | load_feature_overview() |
role_constraints |
Player/Coach must-do/must-not-do | TASK-GE-003 | 2 | load_role_context() |
failed_approaches |
Failed approaches with prevention guidance | TASK-GE-004 | ~10 initial | load_failed_approaches() |
Phase 2 Enhancements (FEAT-GR):
These additional categories were added in Phase 2 (Graphiti Refinement):
| Group ID | Contents | Added By | Purpose |
|---|---|---|---|
turn_states |
AutoBuild turn-by-turn state | FEAT-GR-005 | Cross-turn learning |
captured_knowledge |
Interactively captured facts | FEAT-GR-004 | User knowledge preservation |
role_constraints |
Player/Coach role boundaries | FEAT-GR-004 | AutoBuild role customization |
quality_gate_configs |
Task-type specific thresholds | FEAT-GR-004 | Quality gate customization |
implementation_modes |
Direct vs task-work guidance | FEAT-GR-006 | AutoBuild workflow preferences |
Python API Reference¶
Core Client¶
GraphitiConfig¶
Immutable configuration dataclass for Graphiti connection.
from guardkit.knowledge import GraphitiConfig
config = GraphitiConfig(
enabled=True,
host="localhost",
port=8000,
timeout=30.0
)
Fields:
- enabled (bool): Whether Graphiti integration is enabled (default: True)
- host (str): Graphiti server hostname (default: "localhost")
- port (int): Graphiti server port (default: 8000)
- timeout (float): Connection timeout in seconds (default: 30.0)
Raises: ValueError if timeout is zero or negative.
GraphitiClient¶
Main client wrapper with graceful degradation.
from guardkit.knowledge import GraphitiClient, GraphitiConfig
# Create and initialize client
config = GraphitiConfig(enabled=True, host="localhost", port=8000)
client = GraphitiClient(config)
await client.initialize()
# Check if enabled before operations
if client.enabled:
# Search knowledge graph
results = await client.search(
query="authentication patterns",
group_ids=["architecture_decisions"],
num_results=5
)
# Add episode
episode_id = await client.add_episode(
name="OAuth2 Implementation",
episode_body="Decided to use OAuth2 with PKCE flow...",
group_id="architecture_decisions"
)
Methods:
initialize() -> bool: Establish connection (checks config, OPENAI_API_KEY, server)health_check() -> bool: Verify server is respondingsearch(query, group_ids=None, num_results=10) -> List[Dict]: Search knowledge graphadd_episode(name, episode_body, group_id) -> Optional[str]: Create episode
Properties:
enabled: True only if config enabled AND successfully connected
Graceful Degradation: All methods return empty results or None on failure instead of raising exceptions.
init_graphiti / get_graphiti¶
Singleton pattern for global client access.
from guardkit.knowledge import init_graphiti, get_graphiti, GraphitiConfig
# Initialize once (typically in application startup)
config = GraphitiConfig(enabled=True)
success = await init_graphiti(config)
# Get instance anywhere
client = get_graphiti()
if client and client.enabled:
results = await client.search("query")
Functions:
init_graphiti(config: Optional[GraphitiConfig] = None) -> bool: Initialize global clientget_graphiti() -> Optional[GraphitiClient]: Get global client instance
Context Loading¶
CriticalContext¶
Dataclass holding session context loaded from Graphiti.
from guardkit.knowledge import CriticalContext
@dataclass
class CriticalContext:
system_context: List[Dict[str, Any]] # What GuardKit is
quality_gates: List[Dict[str, Any]] # Quality gate definitions
architecture_decisions: List[Dict[str, Any]] # MUST FOLLOW decisions
failure_patterns: List[Dict[str, Any]] # DO NOT REPEAT failures
successful_patterns: List[Dict[str, Any]] # What worked well
similar_task_outcomes: List[Dict[str, Any]] # Similar tasks and results
relevant_adrs: List[Dict[str, Any]] # Relevant ADRs
applicable_patterns: List[Dict[str, Any]] # Design patterns for context
relevant_rules: List[Dict[str, Any]] # Code rules for context
load_critical_context¶
Load must-know context at session/command start.
from guardkit.knowledge import load_critical_context, format_context_for_injection
# Load context at session start
context = await load_critical_context(command="task-work")
# Load context for specific task
context = await load_critical_context(
task_id="TASK-001",
command="feature-build"
)
# Format and inject into session
context_text = format_context_for_injection(context)
# Inject context_text into Claude Code session prompt
Args:
- task_id (Optional[str]): Task ID for task-specific context
- feature_id (Optional[str]): Feature ID for feature-specific context
- command (Optional[str]): Command name (e.g., "task-work", "feature-build")
Returns: CriticalContext with loaded knowledge, or empty context if Graphiti unavailable.
Always Loads: 1. System context (what GuardKit is) 2. Quality gates (thresholds, phases) 3. Architecture decisions (MUST FOLLOW) 4. Failure patterns (DO NOT REPEAT)
Command-Specific:
- When command="feature-build": Loads feature-build specific architecture
load_feature_overview¶
Load feature overview for context injection (TASK-GE-001).
from guardkit.knowledge import load_feature_overview
overview = await load_feature_overview("feature-build")
if overview:
print(f"Feature: {overview.name}")
print(f"Purpose: {overview.purpose}")
print(f"Invariants: {overview.invariants}")
Args:
- feature_name (str): Name/ID of feature (e.g., "feature-build")
Returns: FeatureOverviewEntity if found, None otherwise.
load_critical_adrs¶
Load Architecture Decision Records for context injection (TASK-GE-007).
from guardkit.knowledge import load_critical_adrs
adrs = await load_critical_adrs()
for adr in adrs:
print(f"ADR {adr['id']}: {adr['title']}")
print(f"Decision: {adr['decision']}")
print(f"Violation symptoms: {adr.get('violation_symptoms', [])}")
Returns: List of ADR dictionaries with id, title, decision, violation_symptoms, etc.
load_failed_approaches¶
Load failed approaches for warning injection (TASK-GE-004).
from guardkit.knowledge import load_failed_approaches
warnings = await load_failed_approaches("subprocess task-work", limit=5)
for warning in warnings:
print(f"Warning: {warning['symptom']}")
print(f"Prevention: {warning['prevention']}")
print(f"Related ADRs: {warning.get('related_adrs', [])}")
Args:
- query_context (str): Context to search for (e.g., "subprocess task-work")
- limit (int): Maximum results to return (default: 5)
Returns: List of warning dictionaries with symptom, prevention, related_adrs.
load_role_context¶
Load role constraints for Player/Coach agents (TASK-GE-003).
from guardkit.knowledge import load_role_context
player_context = await load_role_context("player", "feature-build")
if player_context:
# Returns formatted markdown:
# # PLAYER Role Constraints
#
# **Primary responsibility**: Implement the task
#
# ## MUST DO
# - Write comprehensive tests
# - Run tests and verify they pass
# ...
print(player_context)
Args:
- role (str): Role name ("player" | "coach")
- context (str): Usage context (default: "feature-build")
Returns: Formatted markdown string with role constraints, or None if unavailable.
format_context_for_injection¶
Format loaded context into markdown for session injection.
from guardkit.knowledge import load_critical_context, format_context_for_injection, ContextFormatterConfig
context = await load_critical_context(command="feature-build")
# Default formatting
context_text = format_context_for_injection(context)
# Custom formatting
config = ContextFormatterConfig(
include_system_context=True,
include_quality_gates=True,
include_adrs=True,
include_failures=True,
max_items_per_section=10
)
context_text = format_context_for_injection(context, config)
Args:
- context (CriticalContext): Loaded context
- config (Optional[ContextFormatterConfig]): Formatting configuration
Returns: Formatted markdown string ready for injection.
ADR Service¶
ADREntity¶
Architecture Decision Record dataclass.
from guardkit.knowledge import ADREntity, ADRStatus, ADRTrigger
from datetime import datetime
adr = ADREntity(
id="ADR-0001",
title="Use hash-based task IDs",
status=ADRStatus.ACCEPTED,
trigger=ADRTrigger.TASK_REVIEW,
source_task_id="TASK-001",
context="Need concurrent-safe task creation",
decision="Use SHA256 hash of title + timestamp",
rationale="Eliminates race conditions, works offline",
alternatives_considered=["Sequential IDs", "UUID"],
consequences=["Cannot predict ID", "Slightly longer IDs"],
tags=["task-management", "concurrency"],
confidence=0.9
)
Fields:
- id (str): ADR ID (e.g., "ADR-0001")
- title (str): Decision title
- status (ADRStatus): ACCEPTED | SUPERSEDED | DEPRECATED
- trigger (ADRTrigger): What triggered the ADR
- source_task_id (Optional[str]): Related task
- source_feature_id (Optional[str]): Related feature
- source_command (Optional[str]): Command that created ADR
- context (str): Why this decision was needed
- decision (str): What was decided
- rationale (str): Why this decision was made
- alternatives_considered (List[str]): Alternatives evaluated
- consequences (List[str]): Trade-offs and impacts
- supersedes (Optional[str]): ADR ID this replaces
- superseded_by (Optional[str]): ADR ID that replaces this
- related_adrs (List[str]): Related ADR IDs
- created_at (datetime): When created
- decided_at (Optional[datetime]): When decision finalized
- deprecated_at (Optional[datetime]): When deprecated
- tags (List[str]): Tags for categorization
- confidence (float): Confidence in decision (0-1)
ADRStatus¶
ADR lifecycle status enum.
from guardkit.knowledge import ADRStatus
ADRStatus.ACCEPTED # Active decision
ADRStatus.SUPERSEDED # Replaced by newer ADR
ADRStatus.DEPRECATED # No longer applicable
ADRTrigger¶
ADR trigger source enum.
from guardkit.knowledge import ADRTrigger
ADRTrigger.MANUAL # Manually created
ADRTrigger.CLARIFYING_QUESTION # From clarification Q&A
ADRTrigger.TASK_REVIEW # From task review
ADRTrigger.ARCHITECTURE_REVIEW # From architectural review
ADRTrigger.DECISION_CHECKPOINT # From decision checkpoint
ADRService¶
Service for creating and managing ADRs.
from guardkit.knowledge import ADRService, ADREntity, ADRTrigger, ADRStatus, get_graphiti
client = get_graphiti()
service = ADRService(client, significance_threshold=0.4)
# Create ADR
adr = ADREntity(
id="ADR-0001",
title="Use PostgreSQL",
trigger=ADRTrigger.MANUAL,
decision="Use PostgreSQL for primary database"
)
adr_id = await service.create_adr(adr)
# Search ADRs
results = await service.search_adrs("database decisions", status=ADRStatus.ACCEPTED)
# Get specific ADR
adr = await service.get_adr("ADR-0001")
# Supersede ADR
new_adr = ADREntity(id="ADR-0002", title="Use MongoDB instead")
new_id = await service.supersede_adr("ADR-0001", new_adr)
# Record decision from Q&A (convenience method)
adr_id = await service.record_decision(
question="Which database?",
answer="PostgreSQL for ACID compliance",
trigger=ADRTrigger.CLARIFYING_QUESTION,
source_task_id="TASK-001"
)
Methods:
- create_adr(adr: ADREntity) -> Optional[str]: Create new ADR
- search_adrs(query: str, status: Optional[ADRStatus] = None, num_results: int = 10) -> List[ADREntity]: Search ADRs
- get_adr(adr_id: str) -> Optional[ADREntity]: Get specific ADR
- supersede_adr(old_adr_id: str, new_adr: ADREntity) -> Optional[str]: Create superseding ADR
- deprecate_adr(adr_id: str) -> Optional[bool]: Mark ADR deprecated
- record_decision(question, answer, trigger, **kwargs) -> Optional[str]: Convenience method for Q&A decisions
Outcome Capture¶
OutcomeType¶
Enum for outcome types.
from guardkit.knowledge import OutcomeType
OutcomeType.TASK_COMPLETED # Task finished successfully
OutcomeType.TASK_BLOCKED # Task blocked by issue
OutcomeType.TASK_CANCELLED # Task cancelled
OutcomeType.PHASE_COMPLETED # Workflow phase completed
TaskOutcome¶
Dataclass for task outcomes.
from guardkit.knowledge import TaskOutcome, OutcomeType
from datetime import datetime
outcome = TaskOutcome(
id="OUT-A1B2C3D4",
outcome_type=OutcomeType.TASK_COMPLETED,
task_id="TASK-001",
task_title="Implement OAuth2",
task_requirements="Add OAuth2 authentication with PKCE flow",
success=True,
summary="Successfully implemented OAuth2",
approach_used="Used passport.js library with custom strategy",
patterns_used=["Strategy Pattern", "Dependency Injection"],
problems_encountered=["CORS issues with redirect URIs"],
lessons_learned=["Always test redirect URIs in production-like env"],
tests_written=15,
test_coverage=0.92,
review_cycles=2,
started_at=datetime(2024, 1, 15, 10, 0),
completed_at=datetime(2024, 1, 15, 14, 30),
duration_minutes=270,
feature_id="FEAT-AUTH",
related_adr_ids=["ADR-0042"]
)
Fields:
- id (str): Unique outcome ID
- outcome_type (OutcomeType): Type of outcome
- task_id (str): Related task ID
- task_title (str): Task title
- task_requirements (str): Task requirements
- success (bool): Whether outcome was successful
- summary (str): Brief summary
- approach_used (Optional[str]): Approach description
- patterns_used (Optional[List[str]]): Design patterns applied
- problems_encountered (Optional[List[str]]): Problems faced
- lessons_learned (Optional[List[str]]): Lessons learned
- tests_written (Optional[int]): Number of tests
- test_coverage (Optional[float]): Coverage percentage
- review_cycles (Optional[int]): Number of review cycles
- started_at (Optional[datetime]): Start time
- completed_at (Optional[datetime]): Completion time
- duration_minutes (Optional[int]): Duration in minutes
- feature_id (Optional[str]): Related feature ID
- related_adr_ids (Optional[List[str]]): Related ADR IDs
capture_task_outcome¶
Capture task outcome as episode.
from guardkit.knowledge import capture_task_outcome, OutcomeType
outcome_id = await capture_task_outcome(
outcome_type=OutcomeType.TASK_COMPLETED,
task_id="TASK-001",
task_title="Implement OAuth2",
task_requirements="Add OAuth2 authentication",
success=True,
summary="Successfully implemented OAuth2 with PKCE",
approach_used="passport.js with custom strategy",
patterns_used=["Strategy Pattern"],
problems_encountered=["CORS issues"],
lessons_learned=["Test redirects in prod-like env"],
tests_written=15,
test_coverage=0.92
)
Returns: Unique outcome ID (format: OUT-XXXXXXXX)
Graceful Degradation: Returns ID even if Graphiti unavailable.
find_similar_task_outcomes¶
Search for similar task outcomes (future feature).
from guardkit.knowledge import find_similar_task_outcomes
similar = await find_similar_task_outcomes(
task_requirements="Implement OAuth2 authentication",
limit=5
)
for outcome in similar:
print(f"Task {outcome['task_id']}: {outcome['summary']}")
print(f"Lessons: {outcome.get('lessons_learned', [])}")
Integration Points¶
task-work Integration (Phase 1)¶
Context loading happens at the start of Phase 1 (Planning):
# In task-work command
from guardkit.knowledge import load_critical_context, format_context_for_injection
context = await load_critical_context(
task_id=task_id,
command="task-work"
)
context_text = format_context_for_injection(context)
# Inject context_text into planning phase prompt
# This ensures planning considers:
# - Quality gate requirements
# - Architectural constraints
# - Known failure patterns
# - Similar task outcomes
feature-build Integration¶
Context loading happens before the Player-Coach loop:
# In feature-build command
from guardkit.knowledge import (
load_critical_context,
load_feature_overview,
load_role_context,
load_failed_approaches,
format_context_for_injection
)
# Load system context
system_context = await load_critical_context(
task_id=task_id,
feature_id=feature_id,
command="feature-build"
)
# Load feature-build overview
overview = await load_feature_overview("feature-build")
# Load role constraints
player_constraints = await load_role_context("player", "feature-build")
coach_constraints = await load_role_context("coach", "feature-build")
# Load failure warnings
warnings = await load_failed_approaches("feature-build subprocess")
# Format and inject into Player and Coach agents
Outcome Capture on Completion¶
When tasks complete, outcomes are captured:
# In /task-complete command
from guardkit.knowledge import capture_task_outcome, OutcomeType
outcome_id = await capture_task_outcome(
outcome_type=OutcomeType.TASK_COMPLETED,
task_id=task_id,
task_title=task_title,
task_requirements=task_requirements,
success=True,
summary=summary,
# ... additional fields from task metadata
)
Turn State Capture (TASK-GE-002)¶
AutoBuild captures turn state for cross-turn learning:
from guardkit.knowledge import capture_turn_state, create_turn_state_from_autobuild
# Create turn state from AutoBuild results
entity = create_turn_state_from_autobuild(
feature_id="FEAT-GE",
task_id="TASK-GE-001",
turn_number=1,
player_summary="Implemented feature",
player_decision="implemented",
coach_decision="approved"
)
# Capture to Graphiti
await capture_turn_state(graphiti, entity)
/feature-plan Integration (FEAT-GR-003)¶
Context loading with Graphiti-enhanced context:
# In /feature-plan command
from guardkit.knowledge.feature_detector import FeatureDetector
from guardkit.knowledge.job_context_retriever import JobContextRetriever
# Detect feature from description
detector = FeatureDetector(project_root)
feature_id = detector.detect_feature_id(description)
# Find feature spec and related features
feature_spec = detector.find_feature_spec(feature_id)
related_features = detector.find_related_features(feature_id)
# Retrieve enhanced context from Graphiti
retriever = JobContextRetriever(graphiti)
context = await retriever.retrieve(
task={
"id": feature_id,
"description": description,
"is_autobuild": True,
},
phase=TaskPhase.PLAN
)
# Context includes:
# - Related features from Graphiti
# - Relevant patterns for the task type
# - Role constraints for Player/Coach
# - Quality gate configs for this task type
# - Implementation mode guidance
Interactive Knowledge Capture Integration (FEAT-GR-004)¶
Interactive capture in /task-review --capture-knowledge:
from guardkit.knowledge.interactive_capture import InteractiveCaptureSession
from guardkit.knowledge.gap_analyzer import KnowledgeCategory
# Run interactive capture session
session = InteractiveCaptureSession()
captured = await session.run_session(
focus=KnowledgeCategory.ARCHITECTURE,
max_questions=5,
ui_callback=cli_callback
)
# Or run abbreviated capture after review
result = await session.run_abbreviated(
questions=review_generated_questions,
task_context={'task_id': task_id, 'review_mode': 'architectural'}
)
# Captured knowledge is automatically stored in Graphiti
# - Group ID mapped from category (e.g., ARCHITECTURE → project_architecture)
# - Facts extracted and prefixed with category context
# - Structured metadata includes entity_type, source, captured_at
Job-Specific Context Retrieval Integration (FEAT-GR-006)¶
Context retrieval in /task-work and /feature-build:
from guardkit.knowledge.job_context_retriever import JobContextRetriever
from guardkit.knowledge.task_analyzer import TaskPhase
# In task-work or feature-build
retriever = JobContextRetriever(graphiti)
# Retrieve context with characteristics-based allocation
context = await retriever.retrieve_parallel(
task={
"id": task_id,
"description": task_description,
"tech_stack": detected_stack,
"complexity": complexity_score,
"is_autobuild": is_autobuild_mode,
"feature_id": feature_id,
"turn_number": current_turn,
"has_previous_turns": turn_number > 1,
},
phase=TaskPhase.IMPLEMENT,
collect_metrics=True, # For quality monitoring
)
# Budget automatically allocated based on:
# - Task complexity (2000-6000 base tokens)
# - Novelty adjustments (+30% first-of-type, +15% few similar)
# - Refinement adjustments (+20% for refinement attempts)
# - AutoBuild adjustments (+15% later turns, +10% with history)
# Inject context into prompt
prompt = f"""
{system_prompt}
{context.to_prompt()}
Task: {task_description}
"""
Entity Models¶
ADREntity¶
Complete field reference:
@dataclass
class ADREntity:
id: str # ADR-0001
title: str # Decision title
status: ADRStatus # ACCEPTED | SUPERSEDED | DEPRECATED
trigger: ADRTrigger # What triggered this ADR
source_task_id: Optional[str] # Related task
source_feature_id: Optional[str] # Related feature
source_command: Optional[str] # Command that created ADR
context: str # Why needed
decision: str # What was decided
rationale: str # Why this decision
alternatives_considered: List[str] # Alternatives evaluated
consequences: List[str] # Trade-offs
supersedes: Optional[str] # ADR ID this replaces
superseded_by: Optional[str] # ADR ID replacing this
related_adrs: List[str] # Related ADR IDs
created_at: datetime # Creation time
decided_at: Optional[datetime] # Decision time
deprecated_at: Optional[datetime] # Deprecation time
tags: List[str] # Categorization
confidence: float # 0-1 confidence
TaskOutcome¶
Complete field reference:
@dataclass
class TaskOutcome:
id: str # OUT-A1B2C3D4
outcome_type: OutcomeType # TASK_COMPLETED, etc.
task_id: str # TASK-001
task_title: str # Human-readable title
task_requirements: str # Original requirements
success: bool # Outcome success
summary: str # Brief summary
approach_used: Optional[str] # Approach description
patterns_used: Optional[List[str]] # Design patterns
problems_encountered: Optional[List[str]] # Problems faced
lessons_learned: Optional[List[str]] # Lessons learned
tests_written: Optional[int] # Number of tests
test_coverage: Optional[float] # Coverage (0-1)
review_cycles: Optional[int] # Review cycles
started_at: Optional[datetime] # Start time
completed_at: Optional[datetime] # End time
duration_minutes: Optional[int] # Duration
feature_id: Optional[str] # Related feature
related_adr_ids: Optional[List[str]] # Related ADRs
created_at: datetime # Record creation
CriticalContext¶
Complete field reference:
@dataclass
class CriticalContext:
system_context: List[Dict[str, Any]] # GuardKit identity
quality_gates: List[Dict[str, Any]] # Gate definitions
architecture_decisions: List[Dict[str, Any]] # MUST FOLLOW
failure_patterns: List[Dict[str, Any]] # DO NOT REPEAT
successful_patterns: List[Dict[str, Any]] # What worked
similar_task_outcomes: List[Dict[str, Any]] # Similar tasks
relevant_adrs: List[Dict[str, Any]] # Relevant ADRs
applicable_patterns: List[Dict[str, Any]] # Design patterns
relevant_rules: List[Dict[str, Any]] # Code rules
FeatureOverviewEntity (TASK-GE-001)¶
Feature identity preservation:
@dataclass
class FeatureOverviewEntity:
id: str # feature-build
name: str # Feature Build
tagline: str # One-line description
purpose: str # Why it exists
what_it_is: List[str] # Defining characteristics
what_it_is_not: List[str] # Explicit boundaries
invariants: List[str] # Never-violate rules
architecture_summary: str # High-level design
key_components: List[str] # Core components
key_decisions: List[str] # Critical design choices
created_at: datetime # Creation time
updated_at: datetime # Last update
TurnStateEntity (TASK-GE-002)¶
Turn-by-turn state capture:
@dataclass
class TurnStateEntity:
turn_number: int # Turn number
mode: TurnMode # standard | tdd | bdd
player_summary: str # What Player did
player_decision: str # Player's conclusion
coach_decision: str # Coach's verdict
# ... additional fields
FailedApproachEpisode (TASK-GE-004)¶
Failed approach capture:
@dataclass
class FailedApproachEpisode:
id: str # FAIL-001
approach: str # What was tried
symptom: str # Observable symptom
root_cause: str # Why it failed
prevention: str # How to prevent
severity: Severity # CRITICAL | HIGH | MEDIUM | LOW
occurrence_count: int # Times observed
related_adrs: List[str] # Related ADRs
# ... additional fields
Phase 2 Entity Models¶
TaskCharacteristics (FEAT-GR-006)¶
Analyzed task properties for context retrieval decisions:
from guardkit.knowledge.task_analyzer import TaskCharacteristics, TaskType, TaskPhase
@dataclass
class TaskCharacteristics:
# Basic info
task_id: str # Task identifier (e.g., "TASK-001")
description: str # Task description text
tech_stack: str # Technology stack (e.g., "python", "typescript")
# Classification
task_type: TaskType # IMPLEMENTATION | REVIEW | PLANNING | REFINEMENT | DOCUMENTATION
current_phase: TaskPhase # LOAD | PLAN | IMPLEMENT | TEST | REVIEW
complexity: int # Complexity score (1-10)
# Novelty indicators
is_first_of_type: bool # True if no similar tasks exist in history
similar_task_count: int # Number of similar tasks found
# Context indicators
feature_id: Optional[str] # Associated feature identifier
is_refinement: bool # True if refinement of previous attempt
refinement_attempt: int # Which refinement attempt (0 = first try)
previous_failure_type: Optional[str] # Type of previous failure (if refinement)
# Historical performance
avg_turns_for_type: float # Historical average turns for this task type
success_rate_for_type: float # Historical success rate for this task type
# AutoBuild context
current_actor: str = "player" # Current actor ("player" or "coach")
turn_number: int = 0 # Current turn number
is_autobuild: bool = False # True if running in AutoBuild mode
has_previous_turns: bool = False # True if previous turns exist
TaskType and TaskPhase Enums (FEAT-GR-006)¶
from guardkit.knowledge.task_analyzer import TaskType, TaskPhase
class TaskType(str, Enum):
"""Classification of task types."""
IMPLEMENTATION = "implementation" # Building features, fixing bugs
REVIEW = "review" # Analyzing architecture, making decisions
PLANNING = "planning" # Designing implementation approach
REFINEMENT = "refinement" # Improving or fixing previous attempt
DOCUMENTATION = "documentation" # Writing or updating documentation
class TaskPhase(str, Enum):
"""Execution phase within a task."""
LOAD = "load" # Initial context loading phase
PLAN = "plan" # Implementation planning phase
IMPLEMENT = "implement" # Code implementation phase
TEST = "test" # Testing and verification phase
REVIEW = "review" # Code review phase
ContextBudget (FEAT-GR-006)¶
Budget allocation for context retrieval:
from guardkit.knowledge.budget_calculator import ContextBudget
@dataclass
class ContextBudget:
total_tokens: int # Total token budget for context retrieval
# Standard allocation percentages (must sum to 1.0)
feature_context: float # Allocation for feature context (0.0-1.0)
similar_outcomes: float # Allocation for similar task outcomes (0.0-1.0)
relevant_patterns: float # Allocation for relevant patterns (0.0-1.0)
architecture_context: float # Allocation for architecture context (0.0-1.0)
warnings: float # Allocation for warnings/failures (0.0-1.0)
domain_knowledge: float # Allocation for domain knowledge (0.0-1.0)
# AutoBuild allocations (default to 0.0 for standard tasks)
role_constraints: float = 0.0 # Allocation for role constraints
quality_gate_configs: float = 0.0 # Allocation for quality gate configs
turn_states: float = 0.0 # Allocation for turn states
implementation_modes: float = 0.0 # Allocation for implementation modes
def get_allocation(self, category: str) -> int:
"""Get token allocation for a category."""
...
RetrievedContext (FEAT-GR-006)¶
Retrieved context data from Graphiti:
from guardkit.knowledge.job_context_retriever import RetrievedContext
@dataclass
class RetrievedContext:
task_id: str # Task identifier
budget_used: int # Tokens used for context retrieval
budget_total: int # Total token budget available
# Standard context categories
feature_context: List[Dict[str, Any]] # Feature-related context
similar_outcomes: List[Dict[str, Any]] # Similar task outcomes
relevant_patterns: List[Dict[str, Any]] # Relevant patterns
architecture_context: List[Dict[str, Any]] # Architecture context
warnings: List[Dict[str, Any]] # Warning/failure patterns
domain_knowledge: List[Dict[str, Any]] # Domain knowledge
# AutoBuild context categories
role_constraints: List[Dict[str, Any]] = field(default_factory=list)
quality_gate_configs: List[Dict[str, Any]] = field(default_factory=list)
turn_states: List[Dict[str, Any]] = field(default_factory=list)
implementation_modes: List[Dict[str, Any]] = field(default_factory=list)
# Quality metrics (optional, from TASK-GR6-011)
quality_metrics: Optional[ContextQualityMetrics] = None
def to_prompt(self) -> str:
"""Format retrieved context as a prompt string."""
...
KnowledgeGap (FEAT-GR-004)¶
Represents a gap in project knowledge:
from guardkit.knowledge.gap_analyzer import KnowledgeGap, KnowledgeCategory
@dataclass
class KnowledgeGap:
category: KnowledgeCategory # Knowledge category this gap belongs to
question: str # Question to ask the user
importance: str # Priority level (high/medium/low)
context: str # Explanation of why this question matters
example_answer: Optional[str] = None # Optional example of a good answer
KnowledgeCategory Enum (FEAT-GR-004)¶
from guardkit.knowledge.gap_analyzer import KnowledgeCategory
class KnowledgeCategory(str, Enum):
"""Categories of project knowledge."""
PROJECT_OVERVIEW = "project_overview"
ARCHITECTURE = "architecture"
DOMAIN = "domain"
CONSTRAINTS = "constraints"
DECISIONS = "decisions"
GOALS = "goals"
# AutoBuild workflow customization categories
ROLE_CUSTOMIZATION = "role_customization"
QUALITY_GATES = "quality_gates"
WORKFLOW_PREFERENCES = "workflow_preferences"
CapturedKnowledge (FEAT-GR-004)¶
Represents captured knowledge from user input:
from guardkit.knowledge.interactive_capture import CapturedKnowledge
@dataclass
class CapturedKnowledge:
category: KnowledgeCategory # Knowledge category this belongs to
question: str # Question that was asked
answer: str # User's answer
extracted_facts: List[str] = field(default_factory=list) # Facts extracted from answer
confidence: float = 1.0 # Confidence score (0.0-1.0)
Phase 2 API Reference¶
TaskAnalyzer (FEAT-GR-006)¶
Analyzes task characteristics for context retrieval decisions:
from guardkit.knowledge.task_analyzer import TaskAnalyzer, TaskPhase, TaskCharacteristics
# Initialize with Graphiti client
analyzer = TaskAnalyzer(graphiti_client)
# Analyze task
task = {
"id": "TASK-001",
"description": "Implement user authentication",
"tech_stack": "python",
"complexity": 6,
}
characteristics = await analyzer.analyze(task, TaskPhase.IMPLEMENT)
# Check task properties
if characteristics.is_first_of_type:
print("Novel task type - load more examples")
if characteristics.is_refinement:
print(f"Refinement attempt {characteristics.refinement_attempt}")
Methods:
analyze(task: Dict, phase: TaskPhase) -> TaskCharacteristics: Analyze task to determine characteristics
Class Attributes:
SIMILARITY_THRESHOLD = 0.7: Threshold for counting similar tasksDEFAULT_AVG_TURNS = 3.0: Default average turns when no historyDEFAULT_SUCCESS_RATE = 0.8: Default success rate when no history
DynamicBudgetCalculator (FEAT-GR-006)¶
Calculates context budget based on task characteristics:
from guardkit.knowledge.budget_calculator import DynamicBudgetCalculator, ContextBudget
calculator = DynamicBudgetCalculator()
budget = calculator.calculate(characteristics)
# Get token allocation for a category
pattern_tokens = budget.get_allocation("relevant_patterns")
print(f"Pattern budget: {pattern_tokens} tokens")
Methods:
calculate(characteristics: TaskCharacteristics) -> ContextBudget: Calculate budget based on characteristics
Budget Adjustments:
| Condition | Adjustment |
|---|---|
| First-of-type (novelty) | +30% |
| Few similar tasks (<3) | +15% |
| Refinement attempt | +20% |
| AutoBuild later turns (>1) | +15% |
| Has previous turn history | +10% |
Base Budgets by Complexity:
| Complexity | Base Budget |
|---|---|
| 1-3 (Simple) | 2000 tokens |
| 4-6 (Medium) | 4000 tokens |
| 7-10 (Complex) | 6000 tokens |
JobContextRetriever (FEAT-GR-006)¶
Retrieves job-specific context from Graphiti:
from guardkit.knowledge.job_context_retriever import JobContextRetriever, RetrievedContext
from guardkit.knowledge.task_analyzer import TaskPhase
from guardkit.knowledge.relevance_tuning import RelevanceConfig
# Default thresholds
retriever = JobContextRetriever(graphiti_client)
# Custom thresholds and cache TTL
config = RelevanceConfig(standard_threshold=0.7)
retriever = JobContextRetriever(graphiti_client, relevance_config=config, cache_ttl=600)
# Retrieve context
task = {
"id": "TASK-001",
"description": "Implement user authentication",
"is_autobuild": True,
}
context = await retriever.retrieve(
task,
TaskPhase.IMPLEMENT,
collect_metrics=True, # Collect quality metrics
early_termination=True, # Stop when budget 95% full
)
# Use context in prompt
prompt_text = context.to_prompt()
# Check quality metrics
if context.quality_metrics and context.quality_metrics.is_quality_acceptable():
print("Good quality context retrieved")
# Parallel retrieval (faster for multiple categories)
context = await retriever.retrieve_parallel(task, TaskPhase.IMPLEMENT)
Methods:
retrieve(task, phase, collect_metrics=False, early_termination=False) -> RetrievedContext: Sequential retrievalretrieve_parallel(task, phase, collect_metrics=False, early_termination=False) -> RetrievedContext: Parallel retrieval using asyncio.gather()
Features:
- Caching: LRU cache with configurable TTL (default: 300s)
- Relevance Filtering: Configurable thresholds (standard: 0.6, first-of-type: 0.5)
- Early Termination: Stop when budget >= 95% full
- Quality Metrics: Optional collection for monitoring
KnowledgeGapAnalyzer (FEAT-GR-004)¶
Identifies gaps in project knowledge:
from guardkit.knowledge.gap_analyzer import KnowledgeGapAnalyzer, KnowledgeCategory
analyzer = KnowledgeGapAnalyzer()
# Get all gaps (sorted by importance: high → medium → low)
gaps = await analyzer.analyze_gaps()
# Focus on specific category
arch_gaps = await analyzer.analyze_gaps(
focus=KnowledgeCategory.ARCHITECTURE,
max_questions=5
)
# Check gaps
for gap in gaps:
print(f"[{gap.importance}] {gap.category}: {gap.question}")
Methods:
analyze_gaps(focus=None, max_questions=10) -> List[KnowledgeGap]: Analyze and return knowledge gaps
Supported Categories:
PROJECT_OVERVIEW,ARCHITECTURE,DOMAIN,CONSTRAINTS,DECISIONS,GOALS- AutoBuild customization:
ROLE_CUSTOMIZATION,QUALITY_GATES,WORKFLOW_PREFERENCES
InteractiveCaptureSession (FEAT-GR-004)¶
Manages interactive knowledge capture sessions:
from guardkit.knowledge.interactive_capture import InteractiveCaptureSession, CapturedKnowledge
from guardkit.knowledge.gap_analyzer import KnowledgeCategory
session = InteractiveCaptureSession()
# Define UI callback
def my_callback(event, data=None):
if event == 'question':
print(f"Q: {data['question']}")
elif event == 'get_input':
return input("> ")
elif event == 'intro':
print(data)
elif event == 'summary':
print(data)
# Run full session
captured = await session.run_session(
focus=KnowledgeCategory.ARCHITECTURE,
max_questions=5,
ui_callback=my_callback
)
print(f"Captured {len(captured)} knowledge items")
# Or run abbreviated session with pre-defined questions
result = await session.run_abbreviated(
questions=["What patterns were identified?", "What should be remembered?"],
task_context={'task_id': 'TASK-001', 'review_mode': 'architectural'}
)
Methods:
run_session(focus=None, max_questions=10, ui_callback=None) -> List[CapturedKnowledge]: Run interactive capturerun_abbreviated(questions, task_context=None) -> Dict[str, Any]: Run abbreviated capture with pre-defined questions
UI Callback Events:
intro: Session introduction messagequestion: Question data dict with number, total, category, question, contextget_input: Request user input (return answer string)captured: Captured knowledge confirmationsummary: Session summary messageinfo: Informational message
FeatureDetector (FEAT-GR-003)¶
Detects feature specs from IDs and descriptions:
from guardkit.knowledge.feature_detector import FeatureDetector
from pathlib import Path
detector = FeatureDetector(project_root=Path("/path/to/project"))
# Extract feature ID from description
feature_id = detector.detect_feature_id("Implement FEAT-GR-001 for graphiti")
# Returns: "FEAT-GR-001"
# Find feature spec file
spec_path = detector.find_feature_spec("FEAT-GR-001")
# Returns: Path to the spec file or None
# Find related features (same prefix)
related = detector.find_related_features("FEAT-GR-001")
# Returns: [Path("FEAT-GR-002-...md"), Path("FEAT-GR-003-...md")]
Methods:
detect_feature_id(description: str) -> Optional[str]: Extract feature ID from textfind_feature_spec(feature_id: str) -> Optional[Path]: Find feature spec filefind_related_features(feature_id: str) -> List[Path]: Find features with same prefix
Search Paths:
docs/features.guardkit/featuresfeatures
Extending the System¶
Adding New Knowledge Categories¶
- Define the group_id:
- Create seeding function:
async def seed_my_category(client: GraphitiClient) -> None:
"""Seed my custom knowledge category."""
episodes = [
{
"name": "concept_1",
"body": "Description of concept 1..."
},
# More episodes
]
for episode in episodes:
await client.add_episode(
name=episode["name"],
episode_body=episode["body"],
group_id=MY_CATEGORY_GROUP_ID
)
- Add to orchestrator (optional):
# In guardkit/knowledge/seeding.py
async def seed_all_system_context(client: GraphitiClient) -> None:
# ... existing seeding
await seed_my_category(client)
Creating Custom Entities¶
- Define entity dataclass:
from dataclasses import dataclass
from datetime import datetime
from typing import List
@dataclass
class MyCustomEntity:
id: str
title: str
content: str
tags: List[str]
created_at: datetime = field(default_factory=datetime.now)
def to_episode_body(self) -> str:
"""Convert to episode body for Graphiti."""
import json
from dataclasses import asdict
return json.dumps(asdict(self), default=str)
- Create manager class:
from guardkit.knowledge import get_graphiti
class MyEntityManager:
def __init__(self):
self.group_id = "my_entities"
async def create(self, entity: MyCustomEntity) -> Optional[str]:
"""Create entity in Graphiti."""
client = get_graphiti()
if client is None or not client.enabled:
return None
try:
return await client.add_episode(
name=f"{entity.id}: {entity.title}",
episode_body=entity.to_episode_body(),
group_id=self.group_id
)
except Exception as e:
logger.warning(f"Failed to create entity: {e}")
return None
async def search(self, query: str, limit: int = 10) -> List[MyCustomEntity]:
"""Search for entities."""
client = get_graphiti()
if client is None or not client.enabled:
return []
try:
results = await client.search(
query=query,
group_ids=[self.group_id],
num_results=limit
)
# Parse results into entities
entities = []
for result in results:
# Parse result['body'] into MyCustomEntity
# ... implementation
pass
return entities
except Exception as e:
logger.warning(f"Failed to search entities: {e}")
return []
Seeding Custom Knowledge¶
- Create seeding script:
import asyncio
from guardkit.knowledge import init_graphiti, get_graphiti, GraphitiConfig
async def main():
# Initialize client
config = GraphitiConfig(enabled=True)
await init_graphiti(config)
client = get_graphiti()
if client and client.enabled:
# Seed your custom knowledge
await seed_my_custom_knowledge(client)
print("Seeding complete!")
else:
print("Graphiti not available")
async def seed_my_custom_knowledge(client):
"""Seed custom knowledge."""
episodes = [
{
"name": "my_concept_1",
"body": "Description...",
"group_id": "my_category"
},
# More episodes
]
for episode in episodes:
await client.add_episode(
name=episode["name"],
episode_body=episode["body"],
group_id=episode["group_id"]
)
if __name__ == "__main__":
asyncio.run(main())
- Run seeding:
Note: This architecture documentation reflects the current implementation as of the completion of FEAT-GI (Graphiti Integration) and FEAT-GE (Graphiti Enhancements). For setup instructions, see docs/setup/graphiti-setup.md. For usage guidance, see docs/guides/graphiti-integration-guide.md.