htmlgraph 0.9.3__py3-none-any.whl → 0.27.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- htmlgraph/.htmlgraph/.session-warning-state.json +6 -0
- htmlgraph/.htmlgraph/agents.json +72 -0
- htmlgraph/.htmlgraph/htmlgraph.db +0 -0
- htmlgraph/__init__.py +173 -17
- htmlgraph/__init__.pyi +123 -0
- htmlgraph/agent_detection.py +127 -0
- htmlgraph/agent_registry.py +45 -30
- htmlgraph/agents.py +160 -107
- htmlgraph/analytics/__init__.py +9 -2
- htmlgraph/analytics/cli.py +190 -51
- htmlgraph/analytics/cost_analyzer.py +391 -0
- htmlgraph/analytics/cost_monitor.py +664 -0
- htmlgraph/analytics/cost_reporter.py +675 -0
- htmlgraph/analytics/cross_session.py +617 -0
- htmlgraph/analytics/dependency.py +192 -100
- htmlgraph/analytics/pattern_learning.py +771 -0
- htmlgraph/analytics/session_graph.py +707 -0
- htmlgraph/analytics/strategic/__init__.py +80 -0
- htmlgraph/analytics/strategic/cost_optimizer.py +611 -0
- htmlgraph/analytics/strategic/pattern_detector.py +876 -0
- htmlgraph/analytics/strategic/preference_manager.py +709 -0
- htmlgraph/analytics/strategic/suggestion_engine.py +747 -0
- htmlgraph/analytics/work_type.py +190 -14
- htmlgraph/analytics_index.py +135 -51
- htmlgraph/api/__init__.py +3 -0
- htmlgraph/api/cost_alerts_websocket.py +416 -0
- htmlgraph/api/main.py +2498 -0
- htmlgraph/api/static/htmx.min.js +1 -0
- htmlgraph/api/static/style-redesign.css +1344 -0
- htmlgraph/api/static/style.css +1079 -0
- htmlgraph/api/templates/dashboard-redesign.html +1366 -0
- htmlgraph/api/templates/dashboard.html +794 -0
- htmlgraph/api/templates/partials/activity-feed-hierarchical.html +326 -0
- htmlgraph/api/templates/partials/activity-feed.html +1100 -0
- htmlgraph/api/templates/partials/agents-redesign.html +317 -0
- htmlgraph/api/templates/partials/agents.html +317 -0
- htmlgraph/api/templates/partials/event-traces.html +373 -0
- htmlgraph/api/templates/partials/features-kanban-redesign.html +509 -0
- htmlgraph/api/templates/partials/features.html +578 -0
- htmlgraph/api/templates/partials/metrics-redesign.html +346 -0
- htmlgraph/api/templates/partials/metrics.html +346 -0
- htmlgraph/api/templates/partials/orchestration-redesign.html +443 -0
- htmlgraph/api/templates/partials/orchestration.html +198 -0
- htmlgraph/api/templates/partials/spawners.html +375 -0
- htmlgraph/api/templates/partials/work-items.html +613 -0
- htmlgraph/api/websocket.py +538 -0
- htmlgraph/archive/__init__.py +24 -0
- htmlgraph/archive/bloom.py +234 -0
- htmlgraph/archive/fts.py +297 -0
- htmlgraph/archive/manager.py +583 -0
- htmlgraph/archive/search.py +244 -0
- htmlgraph/atomic_ops.py +560 -0
- htmlgraph/attribute_index.py +208 -0
- htmlgraph/bounded_paths.py +539 -0
- htmlgraph/builders/__init__.py +14 -0
- htmlgraph/builders/base.py +118 -29
- htmlgraph/builders/bug.py +150 -0
- htmlgraph/builders/chore.py +119 -0
- htmlgraph/builders/epic.py +150 -0
- htmlgraph/builders/feature.py +31 -6
- htmlgraph/builders/insight.py +195 -0
- htmlgraph/builders/metric.py +217 -0
- htmlgraph/builders/pattern.py +202 -0
- htmlgraph/builders/phase.py +162 -0
- htmlgraph/builders/spike.py +52 -19
- htmlgraph/builders/track.py +148 -72
- htmlgraph/cigs/__init__.py +81 -0
- htmlgraph/cigs/autonomy.py +385 -0
- htmlgraph/cigs/cost.py +475 -0
- htmlgraph/cigs/messages_basic.py +472 -0
- htmlgraph/cigs/messaging.py +365 -0
- htmlgraph/cigs/models.py +771 -0
- htmlgraph/cigs/pattern_storage.py +427 -0
- htmlgraph/cigs/patterns.py +503 -0
- htmlgraph/cigs/posttool_analyzer.py +234 -0
- htmlgraph/cigs/reporter.py +818 -0
- htmlgraph/cigs/tracker.py +317 -0
- htmlgraph/cli/.htmlgraph/.session-warning-state.json +6 -0
- htmlgraph/cli/.htmlgraph/agents.json +72 -0
- htmlgraph/cli/.htmlgraph/htmlgraph.db +0 -0
- htmlgraph/cli/__init__.py +42 -0
- htmlgraph/cli/__main__.py +6 -0
- htmlgraph/cli/analytics.py +1424 -0
- htmlgraph/cli/base.py +685 -0
- htmlgraph/cli/constants.py +206 -0
- htmlgraph/cli/core.py +954 -0
- htmlgraph/cli/main.py +147 -0
- htmlgraph/cli/models.py +475 -0
- htmlgraph/cli/templates/__init__.py +1 -0
- htmlgraph/cli/templates/cost_dashboard.py +399 -0
- htmlgraph/cli/work/__init__.py +239 -0
- htmlgraph/cli/work/browse.py +115 -0
- htmlgraph/cli/work/features.py +568 -0
- htmlgraph/cli/work/orchestration.py +676 -0
- htmlgraph/cli/work/report.py +728 -0
- htmlgraph/cli/work/sessions.py +466 -0
- htmlgraph/cli/work/snapshot.py +559 -0
- htmlgraph/cli/work/tracks.py +486 -0
- htmlgraph/cli_commands/__init__.py +1 -0
- htmlgraph/cli_commands/feature.py +195 -0
- htmlgraph/cli_framework.py +115 -0
- htmlgraph/collections/__init__.py +18 -0
- htmlgraph/collections/base.py +415 -98
- htmlgraph/collections/bug.py +53 -0
- htmlgraph/collections/chore.py +53 -0
- htmlgraph/collections/epic.py +53 -0
- htmlgraph/collections/feature.py +12 -26
- htmlgraph/collections/insight.py +100 -0
- htmlgraph/collections/metric.py +92 -0
- htmlgraph/collections/pattern.py +97 -0
- htmlgraph/collections/phase.py +53 -0
- htmlgraph/collections/session.py +194 -0
- htmlgraph/collections/spike.py +56 -16
- htmlgraph/collections/task_delegation.py +241 -0
- htmlgraph/collections/todo.py +511 -0
- htmlgraph/collections/traces.py +487 -0
- htmlgraph/config/cost_models.json +56 -0
- htmlgraph/config.py +190 -0
- htmlgraph/context_analytics.py +344 -0
- htmlgraph/converter.py +216 -28
- htmlgraph/cost_analysis/__init__.py +5 -0
- htmlgraph/cost_analysis/analyzer.py +438 -0
- htmlgraph/dashboard.html +2406 -307
- htmlgraph/dashboard.html.backup +6592 -0
- htmlgraph/dashboard.html.bak +7181 -0
- htmlgraph/dashboard.html.bak2 +7231 -0
- htmlgraph/dashboard.html.bak3 +7232 -0
- htmlgraph/db/__init__.py +38 -0
- htmlgraph/db/queries.py +790 -0
- htmlgraph/db/schema.py +1788 -0
- htmlgraph/decorators.py +317 -0
- htmlgraph/dependency_models.py +19 -2
- htmlgraph/deploy.py +142 -125
- htmlgraph/deployment_models.py +474 -0
- htmlgraph/docs/API_REFERENCE.md +841 -0
- htmlgraph/docs/HTTP_API.md +750 -0
- htmlgraph/docs/INTEGRATION_GUIDE.md +752 -0
- htmlgraph/docs/ORCHESTRATION_PATTERNS.md +717 -0
- htmlgraph/docs/README.md +532 -0
- htmlgraph/docs/__init__.py +77 -0
- htmlgraph/docs/docs_version.py +55 -0
- htmlgraph/docs/metadata.py +93 -0
- htmlgraph/docs/migrations.py +232 -0
- htmlgraph/docs/template_engine.py +143 -0
- htmlgraph/docs/templates/_sections/cli_reference.md.j2 +52 -0
- htmlgraph/docs/templates/_sections/core_concepts.md.j2 +29 -0
- htmlgraph/docs/templates/_sections/sdk_basics.md.j2 +69 -0
- htmlgraph/docs/templates/base_agents.md.j2 +78 -0
- htmlgraph/docs/templates/example_user_override.md.j2 +47 -0
- htmlgraph/docs/version_check.py +163 -0
- htmlgraph/edge_index.py +182 -27
- htmlgraph/error_handler.py +544 -0
- htmlgraph/event_log.py +100 -52
- htmlgraph/event_migration.py +13 -4
- htmlgraph/exceptions.py +49 -0
- htmlgraph/file_watcher.py +101 -28
- htmlgraph/find_api.py +75 -63
- htmlgraph/git_events.py +145 -63
- htmlgraph/graph.py +1122 -106
- htmlgraph/hooks/.htmlgraph/.session-warning-state.json +6 -0
- htmlgraph/hooks/.htmlgraph/agents.json +72 -0
- htmlgraph/hooks/.htmlgraph/index.sqlite +0 -0
- htmlgraph/hooks/__init__.py +45 -0
- htmlgraph/hooks/bootstrap.py +169 -0
- htmlgraph/hooks/cigs_pretool_enforcer.py +354 -0
- htmlgraph/hooks/concurrent_sessions.py +208 -0
- htmlgraph/hooks/context.py +350 -0
- htmlgraph/hooks/drift_handler.py +525 -0
- htmlgraph/hooks/event_tracker.py +1314 -0
- htmlgraph/hooks/git_commands.py +175 -0
- htmlgraph/hooks/hooks-config.example.json +12 -0
- htmlgraph/hooks/installer.py +343 -0
- htmlgraph/hooks/orchestrator.py +674 -0
- htmlgraph/hooks/orchestrator_reflector.py +223 -0
- htmlgraph/hooks/post-checkout.sh +28 -0
- htmlgraph/hooks/post-commit.sh +24 -0
- htmlgraph/hooks/post-merge.sh +26 -0
- htmlgraph/hooks/post_tool_use_failure.py +273 -0
- htmlgraph/hooks/post_tool_use_handler.py +257 -0
- htmlgraph/hooks/posttooluse.py +408 -0
- htmlgraph/hooks/pre-commit.sh +94 -0
- htmlgraph/hooks/pre-push.sh +28 -0
- htmlgraph/hooks/pretooluse.py +819 -0
- htmlgraph/hooks/prompt_analyzer.py +637 -0
- htmlgraph/hooks/session_handler.py +668 -0
- htmlgraph/hooks/session_summary.py +395 -0
- htmlgraph/hooks/state_manager.py +504 -0
- htmlgraph/hooks/subagent_detection.py +202 -0
- htmlgraph/hooks/subagent_stop.py +369 -0
- htmlgraph/hooks/task_enforcer.py +255 -0
- htmlgraph/hooks/task_validator.py +177 -0
- htmlgraph/hooks/validator.py +628 -0
- htmlgraph/ids.py +41 -27
- htmlgraph/index.d.ts +286 -0
- htmlgraph/learning.py +767 -0
- htmlgraph/mcp_server.py +69 -23
- htmlgraph/models.py +1586 -87
- htmlgraph/operations/README.md +62 -0
- htmlgraph/operations/__init__.py +79 -0
- htmlgraph/operations/analytics.py +339 -0
- htmlgraph/operations/bootstrap.py +289 -0
- htmlgraph/operations/events.py +244 -0
- htmlgraph/operations/fastapi_server.py +231 -0
- htmlgraph/operations/hooks.py +350 -0
- htmlgraph/operations/initialization.py +597 -0
- htmlgraph/operations/initialization.py.backup +228 -0
- htmlgraph/operations/server.py +303 -0
- htmlgraph/orchestration/__init__.py +58 -0
- htmlgraph/orchestration/claude_launcher.py +179 -0
- htmlgraph/orchestration/command_builder.py +72 -0
- htmlgraph/orchestration/headless_spawner.py +281 -0
- htmlgraph/orchestration/live_events.py +377 -0
- htmlgraph/orchestration/model_selection.py +327 -0
- htmlgraph/orchestration/plugin_manager.py +140 -0
- htmlgraph/orchestration/prompts.py +137 -0
- htmlgraph/orchestration/spawner_event_tracker.py +383 -0
- htmlgraph/orchestration/spawners/__init__.py +16 -0
- htmlgraph/orchestration/spawners/base.py +194 -0
- htmlgraph/orchestration/spawners/claude.py +173 -0
- htmlgraph/orchestration/spawners/codex.py +435 -0
- htmlgraph/orchestration/spawners/copilot.py +294 -0
- htmlgraph/orchestration/spawners/gemini.py +471 -0
- htmlgraph/orchestration/subprocess_runner.py +36 -0
- htmlgraph/orchestration/task_coordination.py +343 -0
- htmlgraph/orchestration.md +563 -0
- htmlgraph/orchestrator-system-prompt-optimized.txt +863 -0
- htmlgraph/orchestrator.py +669 -0
- htmlgraph/orchestrator_config.py +357 -0
- htmlgraph/orchestrator_mode.py +328 -0
- htmlgraph/orchestrator_validator.py +133 -0
- htmlgraph/parallel.py +646 -0
- htmlgraph/parser.py +160 -35
- htmlgraph/path_query.py +608 -0
- htmlgraph/pattern_matcher.py +636 -0
- htmlgraph/planning.py +147 -52
- htmlgraph/pydantic_models.py +476 -0
- htmlgraph/quality_gates.py +350 -0
- htmlgraph/query_builder.py +109 -72
- htmlgraph/query_composer.py +509 -0
- htmlgraph/reflection.py +443 -0
- htmlgraph/refs.py +344 -0
- htmlgraph/repo_hash.py +512 -0
- htmlgraph/repositories/__init__.py +292 -0
- htmlgraph/repositories/analytics_repository.py +455 -0
- htmlgraph/repositories/analytics_repository_standard.py +628 -0
- htmlgraph/repositories/feature_repository.py +581 -0
- htmlgraph/repositories/feature_repository_htmlfile.py +668 -0
- htmlgraph/repositories/feature_repository_memory.py +607 -0
- htmlgraph/repositories/feature_repository_sqlite.py +858 -0
- htmlgraph/repositories/filter_service.py +620 -0
- htmlgraph/repositories/filter_service_standard.py +445 -0
- htmlgraph/repositories/shared_cache.py +621 -0
- htmlgraph/repositories/shared_cache_memory.py +395 -0
- htmlgraph/repositories/track_repository.py +552 -0
- htmlgraph/repositories/track_repository_htmlfile.py +619 -0
- htmlgraph/repositories/track_repository_memory.py +508 -0
- htmlgraph/repositories/track_repository_sqlite.py +711 -0
- htmlgraph/routing.py +8 -19
- htmlgraph/scripts/deploy.py +1 -2
- htmlgraph/sdk/__init__.py +398 -0
- htmlgraph/sdk/__init__.pyi +14 -0
- htmlgraph/sdk/analytics/__init__.py +19 -0
- htmlgraph/sdk/analytics/engine.py +155 -0
- htmlgraph/sdk/analytics/helpers.py +178 -0
- htmlgraph/sdk/analytics/registry.py +109 -0
- htmlgraph/sdk/base.py +484 -0
- htmlgraph/sdk/constants.py +216 -0
- htmlgraph/sdk/core.pyi +308 -0
- htmlgraph/sdk/discovery.py +120 -0
- htmlgraph/sdk/help/__init__.py +12 -0
- htmlgraph/sdk/help/mixin.py +699 -0
- htmlgraph/sdk/mixins/__init__.py +15 -0
- htmlgraph/sdk/mixins/attribution.py +113 -0
- htmlgraph/sdk/mixins/mixin.py +410 -0
- htmlgraph/sdk/operations/__init__.py +12 -0
- htmlgraph/sdk/operations/mixin.py +427 -0
- htmlgraph/sdk/orchestration/__init__.py +17 -0
- htmlgraph/sdk/orchestration/coordinator.py +203 -0
- htmlgraph/sdk/orchestration/spawner.py +204 -0
- htmlgraph/sdk/planning/__init__.py +19 -0
- htmlgraph/sdk/planning/bottlenecks.py +93 -0
- htmlgraph/sdk/planning/mixin.py +211 -0
- htmlgraph/sdk/planning/parallel.py +186 -0
- htmlgraph/sdk/planning/queue.py +210 -0
- htmlgraph/sdk/planning/recommendations.py +87 -0
- htmlgraph/sdk/planning/smart_planning.py +319 -0
- htmlgraph/sdk/session/__init__.py +19 -0
- htmlgraph/sdk/session/continuity.py +57 -0
- htmlgraph/sdk/session/handoff.py +110 -0
- htmlgraph/sdk/session/info.py +309 -0
- htmlgraph/sdk/session/manager.py +103 -0
- htmlgraph/sdk/strategic/__init__.py +26 -0
- htmlgraph/sdk/strategic/mixin.py +563 -0
- htmlgraph/server.py +685 -180
- htmlgraph/services/__init__.py +10 -0
- htmlgraph/services/claiming.py +199 -0
- htmlgraph/session_hooks.py +300 -0
- htmlgraph/session_manager.py +1392 -175
- htmlgraph/session_registry.py +587 -0
- htmlgraph/session_state.py +436 -0
- htmlgraph/session_warning.py +201 -0
- htmlgraph/sessions/__init__.py +23 -0
- htmlgraph/sessions/handoff.py +756 -0
- htmlgraph/setup.py +34 -17
- htmlgraph/spike_index.py +143 -0
- htmlgraph/sync_docs.py +12 -15
- htmlgraph/system_prompts.py +450 -0
- htmlgraph/templates/AGENTS.md.template +366 -0
- htmlgraph/templates/CLAUDE.md.template +97 -0
- htmlgraph/templates/GEMINI.md.template +87 -0
- htmlgraph/templates/orchestration-view.html +350 -0
- htmlgraph/track_builder.py +146 -15
- htmlgraph/track_manager.py +69 -21
- htmlgraph/transcript.py +890 -0
- htmlgraph/transcript_analytics.py +699 -0
- htmlgraph/types.py +323 -0
- htmlgraph/validation.py +115 -0
- htmlgraph/watch.py +8 -5
- htmlgraph/work_type_utils.py +3 -2
- {htmlgraph-0.9.3.data → htmlgraph-0.27.5.data}/data/htmlgraph/dashboard.html +2406 -307
- htmlgraph-0.27.5.data/data/htmlgraph/templates/AGENTS.md.template +366 -0
- htmlgraph-0.27.5.data/data/htmlgraph/templates/CLAUDE.md.template +97 -0
- htmlgraph-0.27.5.data/data/htmlgraph/templates/GEMINI.md.template +87 -0
- {htmlgraph-0.9.3.dist-info → htmlgraph-0.27.5.dist-info}/METADATA +97 -64
- htmlgraph-0.27.5.dist-info/RECORD +337 -0
- {htmlgraph-0.9.3.dist-info → htmlgraph-0.27.5.dist-info}/entry_points.txt +1 -1
- htmlgraph/cli.py +0 -2688
- htmlgraph/sdk.py +0 -709
- htmlgraph-0.9.3.dist-info/RECORD +0 -61
- {htmlgraph-0.9.3.data → htmlgraph-0.27.5.data}/data/htmlgraph/styles.css +0 -0
- {htmlgraph-0.9.3.dist-info → htmlgraph-0.27.5.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,876 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PatternDetector - Detects tool sequences, delegation chains, and error patterns.
|
|
3
|
+
|
|
4
|
+
This module provides comprehensive pattern detection for learning from agent behavior:
|
|
5
|
+
1. Tool sequence patterns - Common sequences of tool calls (Read → Edit → Run)
|
|
6
|
+
2. Delegation chains - Which agent types work well together
|
|
7
|
+
3. Error patterns - Common failure modes and their solutions
|
|
8
|
+
4. Context patterns - What context leads to successful outcomes
|
|
9
|
+
|
|
10
|
+
Usage:
|
|
11
|
+
from htmlgraph.analytics.strategic import PatternDetector
|
|
12
|
+
|
|
13
|
+
detector = PatternDetector(db_path)
|
|
14
|
+
|
|
15
|
+
# Detect all patterns
|
|
16
|
+
patterns = detector.detect_all_patterns()
|
|
17
|
+
|
|
18
|
+
# Detect specific pattern types
|
|
19
|
+
tool_patterns = detector.detect_tool_sequences(min_frequency=5)
|
|
20
|
+
delegation_patterns = detector.detect_delegation_chains()
|
|
21
|
+
error_patterns = detector.detect_error_patterns()
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
import hashlib
|
|
25
|
+
import json
|
|
26
|
+
import logging
|
|
27
|
+
import sqlite3
|
|
28
|
+
from collections import defaultdict
|
|
29
|
+
from dataclasses import dataclass, field
|
|
30
|
+
from datetime import datetime
|
|
31
|
+
from enum import Enum
|
|
32
|
+
from pathlib import Path
|
|
33
|
+
from typing import Any
|
|
34
|
+
|
|
35
|
+
logger = logging.getLogger(__name__)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class PatternType(Enum):
|
|
39
|
+
"""Types of patterns that can be detected."""
|
|
40
|
+
|
|
41
|
+
TOOL_SEQUENCE = "tool_sequence"
|
|
42
|
+
DELEGATION_CHAIN = "delegation_chain"
|
|
43
|
+
ERROR_PATTERN = "error_pattern"
|
|
44
|
+
CONTEXT_PATTERN = "context_pattern"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@dataclass
|
|
48
|
+
class Pattern:
|
|
49
|
+
"""
|
|
50
|
+
Base pattern dataclass with frequency/confidence scoring.
|
|
51
|
+
|
|
52
|
+
Attributes:
|
|
53
|
+
pattern_id: Unique identifier (hash-based)
|
|
54
|
+
pattern_type: Type of pattern (tool_sequence, delegation_chain, etc.)
|
|
55
|
+
frequency: Number of times this pattern occurs
|
|
56
|
+
confidence: Confidence score (0.0-1.0) based on frequency and success rate
|
|
57
|
+
success_rate: Percentage of times pattern led to successful outcomes
|
|
58
|
+
avg_duration_seconds: Average time taken for this pattern
|
|
59
|
+
last_seen: When this pattern was last observed
|
|
60
|
+
sessions: List of session IDs where pattern occurred
|
|
61
|
+
metadata: Additional pattern-specific data
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
pattern_id: str
|
|
65
|
+
pattern_type: PatternType
|
|
66
|
+
frequency: int = 0
|
|
67
|
+
confidence: float = 0.0
|
|
68
|
+
success_rate: float = 0.0
|
|
69
|
+
avg_duration_seconds: float = 0.0
|
|
70
|
+
last_seen: datetime | None = None
|
|
71
|
+
sessions: list[str] = field(default_factory=list)
|
|
72
|
+
metadata: dict[str, Any] = field(default_factory=dict)
|
|
73
|
+
|
|
74
|
+
def to_dict(self) -> dict[str, Any]:
|
|
75
|
+
"""Convert pattern to dictionary for serialization."""
|
|
76
|
+
return {
|
|
77
|
+
"pattern_id": self.pattern_id,
|
|
78
|
+
"pattern_type": self.pattern_type.value,
|
|
79
|
+
"frequency": self.frequency,
|
|
80
|
+
"confidence": self.confidence,
|
|
81
|
+
"success_rate": self.success_rate,
|
|
82
|
+
"avg_duration_seconds": self.avg_duration_seconds,
|
|
83
|
+
"last_seen": self.last_seen.isoformat() if self.last_seen else None,
|
|
84
|
+
"sessions": self.sessions[:10], # Limit for serialization
|
|
85
|
+
"metadata": self.metadata,
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
def calculate_confidence(self) -> float:
|
|
89
|
+
"""
|
|
90
|
+
Calculate confidence score based on frequency and success rate.
|
|
91
|
+
|
|
92
|
+
Formula: confidence = (frequency_factor * 0.4) + (success_rate * 0.6)
|
|
93
|
+
where frequency_factor = min(frequency / 20, 1.0)
|
|
94
|
+
"""
|
|
95
|
+
frequency_factor = min(self.frequency / 20, 1.0)
|
|
96
|
+
self.confidence = (frequency_factor * 0.4) + ((self.success_rate / 100) * 0.6)
|
|
97
|
+
return self.confidence
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@dataclass
|
|
101
|
+
class ToolSequencePattern(Pattern):
|
|
102
|
+
"""
|
|
103
|
+
Pattern representing a sequence of tool calls.
|
|
104
|
+
|
|
105
|
+
Example: ["Read", "Grep", "Edit", "Bash"]
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
sequence: list[str] = field(default_factory=list)
|
|
109
|
+
|
|
110
|
+
def __post_init__(self) -> None:
|
|
111
|
+
"""Set pattern type."""
|
|
112
|
+
self.pattern_type = PatternType.TOOL_SEQUENCE
|
|
113
|
+
|
|
114
|
+
def to_dict(self) -> dict[str, Any]:
|
|
115
|
+
"""Convert to dictionary including sequence."""
|
|
116
|
+
data = super().to_dict()
|
|
117
|
+
data["sequence"] = self.sequence
|
|
118
|
+
return data
|
|
119
|
+
|
|
120
|
+
@staticmethod
|
|
121
|
+
def generate_id(sequence: list[str]) -> str:
|
|
122
|
+
"""Generate unique pattern ID from sequence."""
|
|
123
|
+
seq_str = "->".join(sequence)
|
|
124
|
+
hash_obj = hashlib.md5(seq_str.encode())
|
|
125
|
+
return f"tsp-{hash_obj.hexdigest()[:8]}"
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
@dataclass
|
|
129
|
+
class DelegationChain(Pattern):
|
|
130
|
+
"""
|
|
131
|
+
Pattern representing a delegation chain between agents.
|
|
132
|
+
|
|
133
|
+
Tracks which subagent types work well together and their success rates.
|
|
134
|
+
Example: orchestrator -> researcher -> coder -> tester
|
|
135
|
+
"""
|
|
136
|
+
|
|
137
|
+
agents: list[str] = field(default_factory=list)
|
|
138
|
+
delegation_types: list[str] = field(default_factory=list)
|
|
139
|
+
|
|
140
|
+
def __post_init__(self) -> None:
|
|
141
|
+
"""Set pattern type."""
|
|
142
|
+
self.pattern_type = PatternType.DELEGATION_CHAIN
|
|
143
|
+
|
|
144
|
+
def to_dict(self) -> dict[str, Any]:
|
|
145
|
+
"""Convert to dictionary including agent chain."""
|
|
146
|
+
data = super().to_dict()
|
|
147
|
+
data["agents"] = self.agents
|
|
148
|
+
data["delegation_types"] = self.delegation_types
|
|
149
|
+
return data
|
|
150
|
+
|
|
151
|
+
@staticmethod
|
|
152
|
+
def generate_id(agents: list[str]) -> str:
|
|
153
|
+
"""Generate unique pattern ID from agent chain."""
|
|
154
|
+
chain_str = "->".join(agents)
|
|
155
|
+
hash_obj = hashlib.md5(chain_str.encode())
|
|
156
|
+
return f"dcp-{hash_obj.hexdigest()[:8]}"
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
@dataclass
|
|
160
|
+
class ErrorPattern(Pattern):
|
|
161
|
+
"""
|
|
162
|
+
Pattern representing common error scenarios.
|
|
163
|
+
|
|
164
|
+
Tracks error types, their frequency, and successful resolution strategies.
|
|
165
|
+
"""
|
|
166
|
+
|
|
167
|
+
error_type: str = ""
|
|
168
|
+
error_message_pattern: str = ""
|
|
169
|
+
tool_context: list[str] = field(default_factory=list)
|
|
170
|
+
resolution_strategies: list[str] = field(default_factory=list)
|
|
171
|
+
|
|
172
|
+
def __post_init__(self) -> None:
|
|
173
|
+
"""Set pattern type."""
|
|
174
|
+
self.pattern_type = PatternType.ERROR_PATTERN
|
|
175
|
+
|
|
176
|
+
def to_dict(self) -> dict[str, Any]:
|
|
177
|
+
"""Convert to dictionary including error details."""
|
|
178
|
+
data = super().to_dict()
|
|
179
|
+
data["error_type"] = self.error_type
|
|
180
|
+
data["error_message_pattern"] = self.error_message_pattern
|
|
181
|
+
data["tool_context"] = self.tool_context
|
|
182
|
+
data["resolution_strategies"] = self.resolution_strategies
|
|
183
|
+
return data
|
|
184
|
+
|
|
185
|
+
@staticmethod
|
|
186
|
+
def generate_id(error_type: str, message_pattern: str) -> str:
|
|
187
|
+
"""Generate unique pattern ID from error details."""
|
|
188
|
+
err_str = f"{error_type}:{message_pattern}"
|
|
189
|
+
hash_obj = hashlib.md5(err_str.encode())
|
|
190
|
+
return f"erp-{hash_obj.hexdigest()[:8]}"
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
class PatternDetector:
|
|
194
|
+
"""
|
|
195
|
+
Detects patterns from agent event history.
|
|
196
|
+
|
|
197
|
+
Analyzes agent_events table to identify:
|
|
198
|
+
1. Tool sequence patterns - Common tool call sequences
|
|
199
|
+
2. Delegation chains - Agent collaboration patterns
|
|
200
|
+
3. Error patterns - Common failure modes
|
|
201
|
+
4. Context patterns - Conditions leading to success/failure
|
|
202
|
+
"""
|
|
203
|
+
|
|
204
|
+
def __init__(self, db_path: Path | str | None = None):
|
|
205
|
+
"""
|
|
206
|
+
Initialize pattern detector.
|
|
207
|
+
|
|
208
|
+
Args:
|
|
209
|
+
db_path: Path to HtmlGraph database. If None, uses default location.
|
|
210
|
+
"""
|
|
211
|
+
if db_path is None:
|
|
212
|
+
from htmlgraph.config import get_database_path
|
|
213
|
+
|
|
214
|
+
db_path = get_database_path()
|
|
215
|
+
|
|
216
|
+
self.db_path = Path(db_path)
|
|
217
|
+
self._conn: sqlite3.Connection | None = None
|
|
218
|
+
|
|
219
|
+
def _get_connection(self) -> sqlite3.Connection:
|
|
220
|
+
"""Get database connection with row factory."""
|
|
221
|
+
if self._conn is None:
|
|
222
|
+
self._conn = sqlite3.connect(str(self.db_path))
|
|
223
|
+
self._conn.row_factory = sqlite3.Row
|
|
224
|
+
return self._conn
|
|
225
|
+
|
|
226
|
+
def close(self) -> None:
|
|
227
|
+
"""Close database connection."""
|
|
228
|
+
if self._conn:
|
|
229
|
+
self._conn.close()
|
|
230
|
+
self._conn = None
|
|
231
|
+
|
|
232
|
+
def detect_all_patterns(
|
|
233
|
+
self,
|
|
234
|
+
min_frequency: int = 3,
|
|
235
|
+
days_back: int = 30,
|
|
236
|
+
) -> list[Pattern]:
|
|
237
|
+
"""
|
|
238
|
+
Detect all pattern types from event history.
|
|
239
|
+
|
|
240
|
+
Args:
|
|
241
|
+
min_frequency: Minimum occurrences to be considered a pattern
|
|
242
|
+
days_back: Number of days of history to analyze
|
|
243
|
+
|
|
244
|
+
Returns:
|
|
245
|
+
List of all detected patterns, sorted by confidence
|
|
246
|
+
"""
|
|
247
|
+
patterns: list[Pattern] = []
|
|
248
|
+
|
|
249
|
+
# Detect each pattern type
|
|
250
|
+
patterns.extend(
|
|
251
|
+
self.detect_tool_sequences(min_frequency=min_frequency, days_back=days_back)
|
|
252
|
+
)
|
|
253
|
+
patterns.extend(
|
|
254
|
+
self.detect_delegation_chains(
|
|
255
|
+
min_frequency=min_frequency, days_back=days_back
|
|
256
|
+
)
|
|
257
|
+
)
|
|
258
|
+
patterns.extend(
|
|
259
|
+
self.detect_error_patterns(min_frequency=min_frequency, days_back=days_back)
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
# Sort by confidence
|
|
263
|
+
patterns.sort(key=lambda p: p.confidence, reverse=True)
|
|
264
|
+
|
|
265
|
+
return patterns
|
|
266
|
+
|
|
267
|
+
def detect_tool_sequences(
|
|
268
|
+
self,
|
|
269
|
+
window_size: int = 3,
|
|
270
|
+
min_frequency: int = 3,
|
|
271
|
+
days_back: int = 30,
|
|
272
|
+
) -> list[ToolSequencePattern]:
|
|
273
|
+
"""
|
|
274
|
+
Detect common tool call sequence patterns.
|
|
275
|
+
|
|
276
|
+
Uses sliding window approach to find frequently occurring sequences.
|
|
277
|
+
|
|
278
|
+
Args:
|
|
279
|
+
window_size: Number of consecutive tools in each sequence
|
|
280
|
+
min_frequency: Minimum occurrences to be considered a pattern
|
|
281
|
+
days_back: Number of days of history to analyze
|
|
282
|
+
|
|
283
|
+
Returns:
|
|
284
|
+
List of tool sequence patterns sorted by frequency
|
|
285
|
+
"""
|
|
286
|
+
conn = self._get_connection()
|
|
287
|
+
cursor = conn.cursor()
|
|
288
|
+
|
|
289
|
+
try:
|
|
290
|
+
# Query tool calls ordered by timestamp, grouped by session
|
|
291
|
+
cursor.execute(
|
|
292
|
+
"""
|
|
293
|
+
SELECT tool_name, session_id, timestamp, status
|
|
294
|
+
FROM agent_events
|
|
295
|
+
WHERE event_type = 'tool_call'
|
|
296
|
+
AND tool_name IS NOT NULL
|
|
297
|
+
AND timestamp > datetime('now', ?)
|
|
298
|
+
ORDER BY session_id, timestamp ASC
|
|
299
|
+
""",
|
|
300
|
+
(f"-{days_back} days",),
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
# Group by session and extract sequences
|
|
304
|
+
session_tools: dict[str, list[tuple[str, datetime, str]]] = defaultdict(
|
|
305
|
+
list
|
|
306
|
+
)
|
|
307
|
+
for row in cursor.fetchall():
|
|
308
|
+
tool = row["tool_name"]
|
|
309
|
+
sess_id = row["session_id"]
|
|
310
|
+
timestamp = (
|
|
311
|
+
datetime.fromisoformat(row["timestamp"])
|
|
312
|
+
if isinstance(row["timestamp"], str)
|
|
313
|
+
else row["timestamp"]
|
|
314
|
+
)
|
|
315
|
+
status = row["status"] or "recorded"
|
|
316
|
+
session_tools[sess_id].append((tool, timestamp, status))
|
|
317
|
+
|
|
318
|
+
# Extract sliding windows and count frequencies
|
|
319
|
+
sequence_data: dict[
|
|
320
|
+
tuple[str, ...], list[tuple[str, datetime, list[str]]]
|
|
321
|
+
] = defaultdict(list)
|
|
322
|
+
|
|
323
|
+
for sess_id, tools in session_tools.items():
|
|
324
|
+
for i in range(len(tools) - window_size + 1):
|
|
325
|
+
window = tools[i : i + window_size]
|
|
326
|
+
sequence = tuple(t[0] for t in window)
|
|
327
|
+
timestamp = window[-1][1]
|
|
328
|
+
statuses = [t[2] for t in window]
|
|
329
|
+
sequence_data[sequence].append((sess_id, timestamp, statuses))
|
|
330
|
+
|
|
331
|
+
# Build patterns from sequences meeting minimum frequency
|
|
332
|
+
patterns: list[ToolSequencePattern] = []
|
|
333
|
+
|
|
334
|
+
for seq_tuple, occurrences in sequence_data.items():
|
|
335
|
+
if len(occurrences) >= min_frequency:
|
|
336
|
+
seq_list: list[str] = list(seq_tuple)
|
|
337
|
+
pattern_id = ToolSequencePattern.generate_id(seq_list)
|
|
338
|
+
|
|
339
|
+
# Calculate success rate (recorded status = success)
|
|
340
|
+
total = len(occurrences)
|
|
341
|
+
successes = sum(
|
|
342
|
+
1
|
|
343
|
+
for _, _, statuses in occurrences
|
|
344
|
+
if all(s == "recorded" for s in statuses)
|
|
345
|
+
)
|
|
346
|
+
success_rate = (successes / total) * 100 if total > 0 else 0.0
|
|
347
|
+
|
|
348
|
+
sessions = list(set(occ[0] for occ in occurrences))
|
|
349
|
+
last_seen = max(occ[1] for occ in occurrences)
|
|
350
|
+
|
|
351
|
+
pattern = ToolSequencePattern(
|
|
352
|
+
pattern_id=pattern_id,
|
|
353
|
+
pattern_type=PatternType.TOOL_SEQUENCE,
|
|
354
|
+
sequence=seq_list,
|
|
355
|
+
frequency=len(occurrences),
|
|
356
|
+
success_rate=success_rate,
|
|
357
|
+
last_seen=last_seen,
|
|
358
|
+
sessions=sessions,
|
|
359
|
+
)
|
|
360
|
+
pattern.calculate_confidence()
|
|
361
|
+
patterns.append(pattern)
|
|
362
|
+
|
|
363
|
+
# Sort by frequency
|
|
364
|
+
patterns.sort(key=lambda p: p.frequency, reverse=True)
|
|
365
|
+
return patterns
|
|
366
|
+
|
|
367
|
+
except sqlite3.Error as e:
|
|
368
|
+
logger.error(f"Error detecting tool sequences: {e}")
|
|
369
|
+
return []
|
|
370
|
+
|
|
371
|
+
def detect_delegation_chains(
|
|
372
|
+
self,
|
|
373
|
+
min_frequency: int = 2,
|
|
374
|
+
days_back: int = 30,
|
|
375
|
+
) -> list[DelegationChain]:
|
|
376
|
+
"""
|
|
377
|
+
Detect common delegation chain patterns.
|
|
378
|
+
|
|
379
|
+
Analyzes agent_collaboration table to find successful agent combinations.
|
|
380
|
+
|
|
381
|
+
Args:
|
|
382
|
+
min_frequency: Minimum occurrences to be considered a pattern
|
|
383
|
+
days_back: Number of days of history to analyze
|
|
384
|
+
|
|
385
|
+
Returns:
|
|
386
|
+
List of delegation chain patterns sorted by frequency
|
|
387
|
+
"""
|
|
388
|
+
conn = self._get_connection()
|
|
389
|
+
cursor = conn.cursor()
|
|
390
|
+
|
|
391
|
+
try:
|
|
392
|
+
# Query delegations ordered by timestamp
|
|
393
|
+
cursor.execute(
|
|
394
|
+
"""
|
|
395
|
+
SELECT from_agent, to_agent, handoff_type, status, session_id, timestamp
|
|
396
|
+
FROM agent_collaboration
|
|
397
|
+
WHERE handoff_type = 'delegation'
|
|
398
|
+
AND timestamp > datetime('now', ?)
|
|
399
|
+
ORDER BY session_id, timestamp ASC
|
|
400
|
+
""",
|
|
401
|
+
(f"-{days_back} days",),
|
|
402
|
+
)
|
|
403
|
+
|
|
404
|
+
# Group by session and build chains
|
|
405
|
+
session_delegations: dict[
|
|
406
|
+
str, list[tuple[str, str, str, str, datetime]]
|
|
407
|
+
] = defaultdict(list)
|
|
408
|
+
for row in cursor.fetchall():
|
|
409
|
+
sess_id = row["session_id"]
|
|
410
|
+
timestamp = (
|
|
411
|
+
datetime.fromisoformat(row["timestamp"])
|
|
412
|
+
if isinstance(row["timestamp"], str)
|
|
413
|
+
else row["timestamp"]
|
|
414
|
+
)
|
|
415
|
+
session_delegations[sess_id].append(
|
|
416
|
+
(
|
|
417
|
+
row["from_agent"],
|
|
418
|
+
row["to_agent"],
|
|
419
|
+
row["handoff_type"],
|
|
420
|
+
row["status"] or "pending",
|
|
421
|
+
timestamp,
|
|
422
|
+
)
|
|
423
|
+
)
|
|
424
|
+
|
|
425
|
+
# Build chains from consecutive delegations
|
|
426
|
+
chain_data: dict[tuple[str, ...], list[tuple[str, datetime, list[str]]]] = (
|
|
427
|
+
defaultdict(list)
|
|
428
|
+
)
|
|
429
|
+
|
|
430
|
+
for sess_id, delegations in session_delegations.items():
|
|
431
|
+
if len(delegations) < 2:
|
|
432
|
+
# Single delegation - create 2-agent chain
|
|
433
|
+
if delegations:
|
|
434
|
+
d = delegations[0]
|
|
435
|
+
chain = (d[0], d[1])
|
|
436
|
+
chain_data[chain].append((sess_id, d[4], [d[3]]))
|
|
437
|
+
continue
|
|
438
|
+
|
|
439
|
+
# Build chains of 2-3 consecutive delegations
|
|
440
|
+
for i in range(len(delegations) - 1):
|
|
441
|
+
# 2-agent chain
|
|
442
|
+
chain2 = (delegations[i][0], delegations[i][1])
|
|
443
|
+
statuses2 = [delegations[i][3]]
|
|
444
|
+
chain_data[chain2].append((sess_id, delegations[i][4], statuses2))
|
|
445
|
+
|
|
446
|
+
# 3-agent chain if possible
|
|
447
|
+
if i < len(delegations) - 1:
|
|
448
|
+
if delegations[i][1] == delegations[i + 1][0]:
|
|
449
|
+
chain3 = (
|
|
450
|
+
delegations[i][0],
|
|
451
|
+
delegations[i][1],
|
|
452
|
+
delegations[i + 1][1],
|
|
453
|
+
)
|
|
454
|
+
statuses3 = [delegations[i][3], delegations[i + 1][3]]
|
|
455
|
+
chain_data[chain3].append(
|
|
456
|
+
(sess_id, delegations[i + 1][4], statuses3)
|
|
457
|
+
)
|
|
458
|
+
|
|
459
|
+
# Build patterns from chains meeting minimum frequency
|
|
460
|
+
patterns: list[DelegationChain] = []
|
|
461
|
+
|
|
462
|
+
for chain_tuple, occurrences in chain_data.items():
|
|
463
|
+
if len(occurrences) >= min_frequency:
|
|
464
|
+
agents = list(chain_tuple)
|
|
465
|
+
pattern_id = DelegationChain.generate_id(agents)
|
|
466
|
+
|
|
467
|
+
# Calculate success rate (completed status = success)
|
|
468
|
+
total = len(occurrences)
|
|
469
|
+
successes = sum(
|
|
470
|
+
1
|
|
471
|
+
for _, _, statuses in occurrences
|
|
472
|
+
if all(s == "completed" for s in statuses)
|
|
473
|
+
)
|
|
474
|
+
success_rate = (successes / total) * 100 if total > 0 else 0.0
|
|
475
|
+
|
|
476
|
+
sessions = list(set(occ[0] for occ in occurrences))
|
|
477
|
+
last_seen = max(occ[1] for occ in occurrences)
|
|
478
|
+
|
|
479
|
+
pattern = DelegationChain(
|
|
480
|
+
pattern_id=pattern_id,
|
|
481
|
+
pattern_type=PatternType.DELEGATION_CHAIN,
|
|
482
|
+
agents=agents,
|
|
483
|
+
delegation_types=["delegation"] * (len(agents) - 1),
|
|
484
|
+
frequency=len(occurrences),
|
|
485
|
+
success_rate=success_rate,
|
|
486
|
+
last_seen=last_seen,
|
|
487
|
+
sessions=sessions,
|
|
488
|
+
)
|
|
489
|
+
pattern.calculate_confidence()
|
|
490
|
+
patterns.append(pattern)
|
|
491
|
+
|
|
492
|
+
# Sort by frequency
|
|
493
|
+
patterns.sort(key=lambda p: p.frequency, reverse=True)
|
|
494
|
+
return patterns
|
|
495
|
+
|
|
496
|
+
except sqlite3.Error as e:
|
|
497
|
+
logger.error(f"Error detecting delegation chains: {e}")
|
|
498
|
+
return []
|
|
499
|
+
|
|
500
|
+
def detect_error_patterns(
|
|
501
|
+
self,
|
|
502
|
+
min_frequency: int = 2,
|
|
503
|
+
days_back: int = 30,
|
|
504
|
+
) -> list[ErrorPattern]:
|
|
505
|
+
"""
|
|
506
|
+
Detect common error patterns and their resolutions.
|
|
507
|
+
|
|
508
|
+
Analyzes error events to identify failure modes and successful recovery strategies.
|
|
509
|
+
|
|
510
|
+
Args:
|
|
511
|
+
min_frequency: Minimum occurrences to be considered a pattern
|
|
512
|
+
days_back: Number of days of history to analyze
|
|
513
|
+
|
|
514
|
+
Returns:
|
|
515
|
+
List of error patterns sorted by frequency
|
|
516
|
+
"""
|
|
517
|
+
conn = self._get_connection()
|
|
518
|
+
cursor = conn.cursor()
|
|
519
|
+
|
|
520
|
+
try:
|
|
521
|
+
# Query error events with surrounding context
|
|
522
|
+
cursor.execute(
|
|
523
|
+
"""
|
|
524
|
+
SELECT
|
|
525
|
+
ae.event_id,
|
|
526
|
+
ae.session_id,
|
|
527
|
+
ae.tool_name,
|
|
528
|
+
ae.output_summary,
|
|
529
|
+
ae.timestamp,
|
|
530
|
+
(
|
|
531
|
+
SELECT GROUP_CONCAT(prev.tool_name, ',')
|
|
532
|
+
FROM agent_events prev
|
|
533
|
+
WHERE prev.session_id = ae.session_id
|
|
534
|
+
AND prev.timestamp < ae.timestamp
|
|
535
|
+
AND prev.event_type = 'tool_call'
|
|
536
|
+
ORDER BY prev.timestamp DESC
|
|
537
|
+
LIMIT 3
|
|
538
|
+
) as prev_tools,
|
|
539
|
+
(
|
|
540
|
+
SELECT GROUP_CONCAT(next.tool_name, ',')
|
|
541
|
+
FROM agent_events next
|
|
542
|
+
WHERE next.session_id = ae.session_id
|
|
543
|
+
AND next.timestamp > ae.timestamp
|
|
544
|
+
AND next.event_type = 'tool_call'
|
|
545
|
+
ORDER BY next.timestamp ASC
|
|
546
|
+
LIMIT 3
|
|
547
|
+
) as next_tools
|
|
548
|
+
FROM agent_events ae
|
|
549
|
+
WHERE ae.event_type = 'error'
|
|
550
|
+
AND ae.timestamp > datetime('now', ?)
|
|
551
|
+
ORDER BY ae.timestamp DESC
|
|
552
|
+
""",
|
|
553
|
+
(f"-{days_back} days",),
|
|
554
|
+
)
|
|
555
|
+
|
|
556
|
+
# Categorize errors by type and message pattern
|
|
557
|
+
error_data: dict[
|
|
558
|
+
tuple[str, str], list[tuple[str, datetime, list[str], list[str]]]
|
|
559
|
+
] = defaultdict(list)
|
|
560
|
+
|
|
561
|
+
for row in cursor.fetchall():
|
|
562
|
+
sess_id = row["session_id"]
|
|
563
|
+
row["tool_name"] or "unknown"
|
|
564
|
+
output = row["output_summary"] or ""
|
|
565
|
+
timestamp = (
|
|
566
|
+
datetime.fromisoformat(row["timestamp"])
|
|
567
|
+
if isinstance(row["timestamp"], str)
|
|
568
|
+
else row["timestamp"]
|
|
569
|
+
)
|
|
570
|
+
|
|
571
|
+
# Extract error type from output
|
|
572
|
+
error_type = self._categorize_error(output)
|
|
573
|
+
message_pattern = self._extract_message_pattern(output)
|
|
574
|
+
|
|
575
|
+
prev_tools = row["prev_tools"].split(",") if row["prev_tools"] else []
|
|
576
|
+
next_tools = row["next_tools"].split(",") if row["next_tools"] else []
|
|
577
|
+
|
|
578
|
+
key = (error_type, message_pattern)
|
|
579
|
+
error_data[key].append((sess_id, timestamp, prev_tools, next_tools))
|
|
580
|
+
|
|
581
|
+
# Build patterns from errors meeting minimum frequency
|
|
582
|
+
patterns: list[ErrorPattern] = []
|
|
583
|
+
|
|
584
|
+
for (error_type, message_pattern), occurrences in error_data.items():
|
|
585
|
+
if len(occurrences) >= min_frequency:
|
|
586
|
+
pattern_id = ErrorPattern.generate_id(error_type, message_pattern)
|
|
587
|
+
|
|
588
|
+
# Collect tool context and resolution strategies
|
|
589
|
+
all_prev_tools: list[str] = []
|
|
590
|
+
all_next_tools: list[str] = []
|
|
591
|
+
for _, _, prev, next_t in occurrences:
|
|
592
|
+
all_prev_tools.extend(prev)
|
|
593
|
+
all_next_tools.extend(next_t)
|
|
594
|
+
|
|
595
|
+
# Most common tools before and after error
|
|
596
|
+
tool_context = list(set(all_prev_tools))[:5]
|
|
597
|
+
resolution_strategies = list(set(all_next_tools))[:5]
|
|
598
|
+
|
|
599
|
+
# Calculate success rate (has resolution = success)
|
|
600
|
+
total = len(occurrences)
|
|
601
|
+
successes = sum(1 for _, _, _, next_t in occurrences if next_t)
|
|
602
|
+
success_rate = (successes / total) * 100 if total > 0 else 0.0
|
|
603
|
+
|
|
604
|
+
sessions = list(set(occ[0] for occ in occurrences))
|
|
605
|
+
last_seen = max(occ[1] for occ in occurrences)
|
|
606
|
+
|
|
607
|
+
pattern = ErrorPattern(
|
|
608
|
+
pattern_id=pattern_id,
|
|
609
|
+
pattern_type=PatternType.ERROR_PATTERN,
|
|
610
|
+
error_type=error_type,
|
|
611
|
+
error_message_pattern=message_pattern,
|
|
612
|
+
tool_context=tool_context,
|
|
613
|
+
resolution_strategies=resolution_strategies,
|
|
614
|
+
frequency=len(occurrences),
|
|
615
|
+
success_rate=success_rate,
|
|
616
|
+
last_seen=last_seen,
|
|
617
|
+
sessions=sessions,
|
|
618
|
+
)
|
|
619
|
+
pattern.calculate_confidence()
|
|
620
|
+
patterns.append(pattern)
|
|
621
|
+
|
|
622
|
+
# Sort by frequency
|
|
623
|
+
patterns.sort(key=lambda p: p.frequency, reverse=True)
|
|
624
|
+
return patterns
|
|
625
|
+
|
|
626
|
+
except sqlite3.Error as e:
|
|
627
|
+
logger.error(f"Error detecting error patterns: {e}")
|
|
628
|
+
return []
|
|
629
|
+
|
|
630
|
+
def _categorize_error(self, output: str) -> str:
|
|
631
|
+
"""
|
|
632
|
+
Categorize error by type based on output content.
|
|
633
|
+
|
|
634
|
+
Args:
|
|
635
|
+
output: Error output/message
|
|
636
|
+
|
|
637
|
+
Returns:
|
|
638
|
+
Error type category
|
|
639
|
+
"""
|
|
640
|
+
output_lower = output.lower()
|
|
641
|
+
|
|
642
|
+
if "permission" in output_lower or "access denied" in output_lower:
|
|
643
|
+
return "permission_error"
|
|
644
|
+
if "not found" in output_lower or "no such file" in output_lower:
|
|
645
|
+
return "not_found_error"
|
|
646
|
+
if "syntax" in output_lower or "parse" in output_lower:
|
|
647
|
+
return "syntax_error"
|
|
648
|
+
if "timeout" in output_lower or "timed out" in output_lower:
|
|
649
|
+
return "timeout_error"
|
|
650
|
+
if "memory" in output_lower or "oom" in output_lower:
|
|
651
|
+
return "memory_error"
|
|
652
|
+
if "network" in output_lower or "connection" in output_lower:
|
|
653
|
+
return "network_error"
|
|
654
|
+
if "type" in output_lower and "error" in output_lower:
|
|
655
|
+
return "type_error"
|
|
656
|
+
if "import" in output_lower:
|
|
657
|
+
return "import_error"
|
|
658
|
+
if "test" in output_lower and (
|
|
659
|
+
"fail" in output_lower or "error" in output_lower
|
|
660
|
+
):
|
|
661
|
+
return "test_failure"
|
|
662
|
+
|
|
663
|
+
return "general_error"
|
|
664
|
+
|
|
665
|
+
def _extract_message_pattern(self, output: str) -> str:
|
|
666
|
+
"""
|
|
667
|
+
Extract a generalized message pattern from error output.
|
|
668
|
+
|
|
669
|
+
Removes specific file names, line numbers, etc. to create a matchable pattern.
|
|
670
|
+
|
|
671
|
+
Args:
|
|
672
|
+
output: Error output/message
|
|
673
|
+
|
|
674
|
+
Returns:
|
|
675
|
+
Generalized message pattern
|
|
676
|
+
"""
|
|
677
|
+
import re
|
|
678
|
+
|
|
679
|
+
# Limit length
|
|
680
|
+
pattern = output[:200]
|
|
681
|
+
|
|
682
|
+
# Remove line numbers
|
|
683
|
+
pattern = re.sub(r"line \d+", "line N", pattern)
|
|
684
|
+
|
|
685
|
+
# Remove file paths
|
|
686
|
+
pattern = re.sub(r"[/\\][\w/\\.-]+\.\w+", "<file>", pattern)
|
|
687
|
+
|
|
688
|
+
# Remove numbers (preserve error codes)
|
|
689
|
+
pattern = re.sub(r"(?<!\w)\d+(?!\w)", "N", pattern)
|
|
690
|
+
|
|
691
|
+
# Normalize whitespace
|
|
692
|
+
pattern = " ".join(pattern.split())
|
|
693
|
+
|
|
694
|
+
return pattern[:100]
|
|
695
|
+
|
|
696
|
+
def get_pattern_by_id(self, pattern_id: str) -> Pattern | None:
|
|
697
|
+
"""
|
|
698
|
+
Retrieve a stored pattern by ID.
|
|
699
|
+
|
|
700
|
+
Args:
|
|
701
|
+
pattern_id: Pattern ID to retrieve
|
|
702
|
+
|
|
703
|
+
Returns:
|
|
704
|
+
Pattern or None if not found
|
|
705
|
+
"""
|
|
706
|
+
conn = self._get_connection()
|
|
707
|
+
cursor = conn.cursor()
|
|
708
|
+
|
|
709
|
+
try:
|
|
710
|
+
cursor.execute(
|
|
711
|
+
"""
|
|
712
|
+
SELECT * FROM delegation_patterns WHERE pattern_id = ?
|
|
713
|
+
""",
|
|
714
|
+
(pattern_id,),
|
|
715
|
+
)
|
|
716
|
+
|
|
717
|
+
row = cursor.fetchone()
|
|
718
|
+
if not row:
|
|
719
|
+
return None
|
|
720
|
+
|
|
721
|
+
# Reconstruct pattern from stored data
|
|
722
|
+
pattern_type = PatternType(row["pattern_type"])
|
|
723
|
+
metadata = json.loads(row["metadata"]) if row["metadata"] else {}
|
|
724
|
+
|
|
725
|
+
if pattern_type == PatternType.TOOL_SEQUENCE:
|
|
726
|
+
return ToolSequencePattern(
|
|
727
|
+
pattern_id=row["pattern_id"],
|
|
728
|
+
pattern_type=pattern_type,
|
|
729
|
+
sequence=metadata.get("sequence", []),
|
|
730
|
+
frequency=row["frequency"],
|
|
731
|
+
confidence=row["confidence"],
|
|
732
|
+
success_rate=row["success_rate"],
|
|
733
|
+
avg_duration_seconds=row["avg_duration_seconds"] or 0.0,
|
|
734
|
+
last_seen=datetime.fromisoformat(row["last_seen"])
|
|
735
|
+
if row["last_seen"]
|
|
736
|
+
else None,
|
|
737
|
+
sessions=json.loads(row["sessions"]) if row["sessions"] else [],
|
|
738
|
+
metadata=metadata,
|
|
739
|
+
)
|
|
740
|
+
elif pattern_type == PatternType.DELEGATION_CHAIN:
|
|
741
|
+
return DelegationChain(
|
|
742
|
+
pattern_id=row["pattern_id"],
|
|
743
|
+
pattern_type=pattern_type,
|
|
744
|
+
agents=metadata.get("agents", []),
|
|
745
|
+
delegation_types=metadata.get("delegation_types", []),
|
|
746
|
+
frequency=row["frequency"],
|
|
747
|
+
confidence=row["confidence"],
|
|
748
|
+
success_rate=row["success_rate"],
|
|
749
|
+
avg_duration_seconds=row["avg_duration_seconds"] or 0.0,
|
|
750
|
+
last_seen=datetime.fromisoformat(row["last_seen"])
|
|
751
|
+
if row["last_seen"]
|
|
752
|
+
else None,
|
|
753
|
+
sessions=json.loads(row["sessions"]) if row["sessions"] else [],
|
|
754
|
+
metadata=metadata,
|
|
755
|
+
)
|
|
756
|
+
elif pattern_type == PatternType.ERROR_PATTERN:
|
|
757
|
+
return ErrorPattern(
|
|
758
|
+
pattern_id=row["pattern_id"],
|
|
759
|
+
pattern_type=pattern_type,
|
|
760
|
+
error_type=metadata.get("error_type", ""),
|
|
761
|
+
error_message_pattern=metadata.get("error_message_pattern", ""),
|
|
762
|
+
tool_context=metadata.get("tool_context", []),
|
|
763
|
+
resolution_strategies=metadata.get("resolution_strategies", []),
|
|
764
|
+
frequency=row["frequency"],
|
|
765
|
+
confidence=row["confidence"],
|
|
766
|
+
success_rate=row["success_rate"],
|
|
767
|
+
avg_duration_seconds=row["avg_duration_seconds"] or 0.0,
|
|
768
|
+
last_seen=datetime.fromisoformat(row["last_seen"])
|
|
769
|
+
if row["last_seen"]
|
|
770
|
+
else None,
|
|
771
|
+
sessions=json.loads(row["sessions"]) if row["sessions"] else [],
|
|
772
|
+
metadata=metadata,
|
|
773
|
+
)
|
|
774
|
+
else:
|
|
775
|
+
return Pattern(
|
|
776
|
+
pattern_id=row["pattern_id"],
|
|
777
|
+
pattern_type=pattern_type,
|
|
778
|
+
frequency=row["frequency"],
|
|
779
|
+
confidence=row["confidence"],
|
|
780
|
+
success_rate=row["success_rate"],
|
|
781
|
+
avg_duration_seconds=row["avg_duration_seconds"] or 0.0,
|
|
782
|
+
last_seen=datetime.fromisoformat(row["last_seen"])
|
|
783
|
+
if row["last_seen"]
|
|
784
|
+
else None,
|
|
785
|
+
sessions=json.loads(row["sessions"]) if row["sessions"] else [],
|
|
786
|
+
metadata=metadata,
|
|
787
|
+
)
|
|
788
|
+
|
|
789
|
+
except sqlite3.Error as e:
|
|
790
|
+
logger.error(f"Error retrieving pattern: {e}")
|
|
791
|
+
return None
|
|
792
|
+
|
|
793
|
+
def store_pattern(self, pattern: Pattern) -> bool:
|
|
794
|
+
"""
|
|
795
|
+
Store or update a pattern in the database.
|
|
796
|
+
|
|
797
|
+
Args:
|
|
798
|
+
pattern: Pattern to store
|
|
799
|
+
|
|
800
|
+
Returns:
|
|
801
|
+
True if stored successfully, False otherwise
|
|
802
|
+
"""
|
|
803
|
+
conn = self._get_connection()
|
|
804
|
+
cursor = conn.cursor()
|
|
805
|
+
|
|
806
|
+
try:
|
|
807
|
+
# Build metadata based on pattern type
|
|
808
|
+
metadata = pattern.metadata.copy()
|
|
809
|
+
|
|
810
|
+
if isinstance(pattern, ToolSequencePattern):
|
|
811
|
+
metadata["sequence"] = pattern.sequence
|
|
812
|
+
elif isinstance(pattern, DelegationChain):
|
|
813
|
+
metadata["agents"] = pattern.agents
|
|
814
|
+
metadata["delegation_types"] = pattern.delegation_types
|
|
815
|
+
elif isinstance(pattern, ErrorPattern):
|
|
816
|
+
metadata["error_type"] = pattern.error_type
|
|
817
|
+
metadata["error_message_pattern"] = pattern.error_message_pattern
|
|
818
|
+
metadata["tool_context"] = pattern.tool_context
|
|
819
|
+
metadata["resolution_strategies"] = pattern.resolution_strategies
|
|
820
|
+
|
|
821
|
+
cursor.execute(
|
|
822
|
+
"""
|
|
823
|
+
INSERT OR REPLACE INTO delegation_patterns
|
|
824
|
+
(pattern_id, pattern_type, frequency, confidence, success_rate,
|
|
825
|
+
avg_duration_seconds, last_seen, sessions, metadata, updated_at)
|
|
826
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'))
|
|
827
|
+
""",
|
|
828
|
+
(
|
|
829
|
+
pattern.pattern_id,
|
|
830
|
+
pattern.pattern_type.value,
|
|
831
|
+
pattern.frequency,
|
|
832
|
+
pattern.confidence,
|
|
833
|
+
pattern.success_rate,
|
|
834
|
+
pattern.avg_duration_seconds,
|
|
835
|
+
pattern.last_seen.isoformat() if pattern.last_seen else None,
|
|
836
|
+
json.dumps(pattern.sessions[:50]), # Limit stored sessions
|
|
837
|
+
json.dumps(metadata),
|
|
838
|
+
),
|
|
839
|
+
)
|
|
840
|
+
|
|
841
|
+
conn.commit()
|
|
842
|
+
return True
|
|
843
|
+
|
|
844
|
+
except sqlite3.Error as e:
|
|
845
|
+
logger.error(f"Error storing pattern: {e}")
|
|
846
|
+
return False
|
|
847
|
+
|
|
848
|
+
def score_pattern(self, pattern: Pattern) -> float:
|
|
849
|
+
"""
|
|
850
|
+
Score a pattern for recommendation ranking.
|
|
851
|
+
|
|
852
|
+
Score combines:
|
|
853
|
+
- Confidence (40%)
|
|
854
|
+
- Recency (30%) - More recent patterns score higher
|
|
855
|
+
- User feedback (30%) - From preference manager
|
|
856
|
+
|
|
857
|
+
Args:
|
|
858
|
+
pattern: Pattern to score
|
|
859
|
+
|
|
860
|
+
Returns:
|
|
861
|
+
Score between 0.0 and 1.0
|
|
862
|
+
"""
|
|
863
|
+
# Confidence component (40%)
|
|
864
|
+
confidence_score = pattern.confidence * 0.4
|
|
865
|
+
|
|
866
|
+
# Recency component (30%)
|
|
867
|
+
recency_score = 0.0
|
|
868
|
+
if pattern.last_seen:
|
|
869
|
+
days_ago = (datetime.now() - pattern.last_seen).days
|
|
870
|
+
recency_factor = max(0, 1 - (days_ago / 30)) # Decay over 30 days
|
|
871
|
+
recency_score = recency_factor * 0.3
|
|
872
|
+
|
|
873
|
+
# User feedback component (30%) - Placeholder, actual implementation in PreferenceManager
|
|
874
|
+
feedback_score = 0.15 # Default neutral
|
|
875
|
+
|
|
876
|
+
return confidence_score + recency_score + feedback_score
|