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.
Files changed (331) hide show
  1. htmlgraph/.htmlgraph/.session-warning-state.json +6 -0
  2. htmlgraph/.htmlgraph/agents.json +72 -0
  3. htmlgraph/.htmlgraph/htmlgraph.db +0 -0
  4. htmlgraph/__init__.py +173 -17
  5. htmlgraph/__init__.pyi +123 -0
  6. htmlgraph/agent_detection.py +127 -0
  7. htmlgraph/agent_registry.py +45 -30
  8. htmlgraph/agents.py +160 -107
  9. htmlgraph/analytics/__init__.py +9 -2
  10. htmlgraph/analytics/cli.py +190 -51
  11. htmlgraph/analytics/cost_analyzer.py +391 -0
  12. htmlgraph/analytics/cost_monitor.py +664 -0
  13. htmlgraph/analytics/cost_reporter.py +675 -0
  14. htmlgraph/analytics/cross_session.py +617 -0
  15. htmlgraph/analytics/dependency.py +192 -100
  16. htmlgraph/analytics/pattern_learning.py +771 -0
  17. htmlgraph/analytics/session_graph.py +707 -0
  18. htmlgraph/analytics/strategic/__init__.py +80 -0
  19. htmlgraph/analytics/strategic/cost_optimizer.py +611 -0
  20. htmlgraph/analytics/strategic/pattern_detector.py +876 -0
  21. htmlgraph/analytics/strategic/preference_manager.py +709 -0
  22. htmlgraph/analytics/strategic/suggestion_engine.py +747 -0
  23. htmlgraph/analytics/work_type.py +190 -14
  24. htmlgraph/analytics_index.py +135 -51
  25. htmlgraph/api/__init__.py +3 -0
  26. htmlgraph/api/cost_alerts_websocket.py +416 -0
  27. htmlgraph/api/main.py +2498 -0
  28. htmlgraph/api/static/htmx.min.js +1 -0
  29. htmlgraph/api/static/style-redesign.css +1344 -0
  30. htmlgraph/api/static/style.css +1079 -0
  31. htmlgraph/api/templates/dashboard-redesign.html +1366 -0
  32. htmlgraph/api/templates/dashboard.html +794 -0
  33. htmlgraph/api/templates/partials/activity-feed-hierarchical.html +326 -0
  34. htmlgraph/api/templates/partials/activity-feed.html +1100 -0
  35. htmlgraph/api/templates/partials/agents-redesign.html +317 -0
  36. htmlgraph/api/templates/partials/agents.html +317 -0
  37. htmlgraph/api/templates/partials/event-traces.html +373 -0
  38. htmlgraph/api/templates/partials/features-kanban-redesign.html +509 -0
  39. htmlgraph/api/templates/partials/features.html +578 -0
  40. htmlgraph/api/templates/partials/metrics-redesign.html +346 -0
  41. htmlgraph/api/templates/partials/metrics.html +346 -0
  42. htmlgraph/api/templates/partials/orchestration-redesign.html +443 -0
  43. htmlgraph/api/templates/partials/orchestration.html +198 -0
  44. htmlgraph/api/templates/partials/spawners.html +375 -0
  45. htmlgraph/api/templates/partials/work-items.html +613 -0
  46. htmlgraph/api/websocket.py +538 -0
  47. htmlgraph/archive/__init__.py +24 -0
  48. htmlgraph/archive/bloom.py +234 -0
  49. htmlgraph/archive/fts.py +297 -0
  50. htmlgraph/archive/manager.py +583 -0
  51. htmlgraph/archive/search.py +244 -0
  52. htmlgraph/atomic_ops.py +560 -0
  53. htmlgraph/attribute_index.py +208 -0
  54. htmlgraph/bounded_paths.py +539 -0
  55. htmlgraph/builders/__init__.py +14 -0
  56. htmlgraph/builders/base.py +118 -29
  57. htmlgraph/builders/bug.py +150 -0
  58. htmlgraph/builders/chore.py +119 -0
  59. htmlgraph/builders/epic.py +150 -0
  60. htmlgraph/builders/feature.py +31 -6
  61. htmlgraph/builders/insight.py +195 -0
  62. htmlgraph/builders/metric.py +217 -0
  63. htmlgraph/builders/pattern.py +202 -0
  64. htmlgraph/builders/phase.py +162 -0
  65. htmlgraph/builders/spike.py +52 -19
  66. htmlgraph/builders/track.py +148 -72
  67. htmlgraph/cigs/__init__.py +81 -0
  68. htmlgraph/cigs/autonomy.py +385 -0
  69. htmlgraph/cigs/cost.py +475 -0
  70. htmlgraph/cigs/messages_basic.py +472 -0
  71. htmlgraph/cigs/messaging.py +365 -0
  72. htmlgraph/cigs/models.py +771 -0
  73. htmlgraph/cigs/pattern_storage.py +427 -0
  74. htmlgraph/cigs/patterns.py +503 -0
  75. htmlgraph/cigs/posttool_analyzer.py +234 -0
  76. htmlgraph/cigs/reporter.py +818 -0
  77. htmlgraph/cigs/tracker.py +317 -0
  78. htmlgraph/cli/.htmlgraph/.session-warning-state.json +6 -0
  79. htmlgraph/cli/.htmlgraph/agents.json +72 -0
  80. htmlgraph/cli/.htmlgraph/htmlgraph.db +0 -0
  81. htmlgraph/cli/__init__.py +42 -0
  82. htmlgraph/cli/__main__.py +6 -0
  83. htmlgraph/cli/analytics.py +1424 -0
  84. htmlgraph/cli/base.py +685 -0
  85. htmlgraph/cli/constants.py +206 -0
  86. htmlgraph/cli/core.py +954 -0
  87. htmlgraph/cli/main.py +147 -0
  88. htmlgraph/cli/models.py +475 -0
  89. htmlgraph/cli/templates/__init__.py +1 -0
  90. htmlgraph/cli/templates/cost_dashboard.py +399 -0
  91. htmlgraph/cli/work/__init__.py +239 -0
  92. htmlgraph/cli/work/browse.py +115 -0
  93. htmlgraph/cli/work/features.py +568 -0
  94. htmlgraph/cli/work/orchestration.py +676 -0
  95. htmlgraph/cli/work/report.py +728 -0
  96. htmlgraph/cli/work/sessions.py +466 -0
  97. htmlgraph/cli/work/snapshot.py +559 -0
  98. htmlgraph/cli/work/tracks.py +486 -0
  99. htmlgraph/cli_commands/__init__.py +1 -0
  100. htmlgraph/cli_commands/feature.py +195 -0
  101. htmlgraph/cli_framework.py +115 -0
  102. htmlgraph/collections/__init__.py +18 -0
  103. htmlgraph/collections/base.py +415 -98
  104. htmlgraph/collections/bug.py +53 -0
  105. htmlgraph/collections/chore.py +53 -0
  106. htmlgraph/collections/epic.py +53 -0
  107. htmlgraph/collections/feature.py +12 -26
  108. htmlgraph/collections/insight.py +100 -0
  109. htmlgraph/collections/metric.py +92 -0
  110. htmlgraph/collections/pattern.py +97 -0
  111. htmlgraph/collections/phase.py +53 -0
  112. htmlgraph/collections/session.py +194 -0
  113. htmlgraph/collections/spike.py +56 -16
  114. htmlgraph/collections/task_delegation.py +241 -0
  115. htmlgraph/collections/todo.py +511 -0
  116. htmlgraph/collections/traces.py +487 -0
  117. htmlgraph/config/cost_models.json +56 -0
  118. htmlgraph/config.py +190 -0
  119. htmlgraph/context_analytics.py +344 -0
  120. htmlgraph/converter.py +216 -28
  121. htmlgraph/cost_analysis/__init__.py +5 -0
  122. htmlgraph/cost_analysis/analyzer.py +438 -0
  123. htmlgraph/dashboard.html +2406 -307
  124. htmlgraph/dashboard.html.backup +6592 -0
  125. htmlgraph/dashboard.html.bak +7181 -0
  126. htmlgraph/dashboard.html.bak2 +7231 -0
  127. htmlgraph/dashboard.html.bak3 +7232 -0
  128. htmlgraph/db/__init__.py +38 -0
  129. htmlgraph/db/queries.py +790 -0
  130. htmlgraph/db/schema.py +1788 -0
  131. htmlgraph/decorators.py +317 -0
  132. htmlgraph/dependency_models.py +19 -2
  133. htmlgraph/deploy.py +142 -125
  134. htmlgraph/deployment_models.py +474 -0
  135. htmlgraph/docs/API_REFERENCE.md +841 -0
  136. htmlgraph/docs/HTTP_API.md +750 -0
  137. htmlgraph/docs/INTEGRATION_GUIDE.md +752 -0
  138. htmlgraph/docs/ORCHESTRATION_PATTERNS.md +717 -0
  139. htmlgraph/docs/README.md +532 -0
  140. htmlgraph/docs/__init__.py +77 -0
  141. htmlgraph/docs/docs_version.py +55 -0
  142. htmlgraph/docs/metadata.py +93 -0
  143. htmlgraph/docs/migrations.py +232 -0
  144. htmlgraph/docs/template_engine.py +143 -0
  145. htmlgraph/docs/templates/_sections/cli_reference.md.j2 +52 -0
  146. htmlgraph/docs/templates/_sections/core_concepts.md.j2 +29 -0
  147. htmlgraph/docs/templates/_sections/sdk_basics.md.j2 +69 -0
  148. htmlgraph/docs/templates/base_agents.md.j2 +78 -0
  149. htmlgraph/docs/templates/example_user_override.md.j2 +47 -0
  150. htmlgraph/docs/version_check.py +163 -0
  151. htmlgraph/edge_index.py +182 -27
  152. htmlgraph/error_handler.py +544 -0
  153. htmlgraph/event_log.py +100 -52
  154. htmlgraph/event_migration.py +13 -4
  155. htmlgraph/exceptions.py +49 -0
  156. htmlgraph/file_watcher.py +101 -28
  157. htmlgraph/find_api.py +75 -63
  158. htmlgraph/git_events.py +145 -63
  159. htmlgraph/graph.py +1122 -106
  160. htmlgraph/hooks/.htmlgraph/.session-warning-state.json +6 -0
  161. htmlgraph/hooks/.htmlgraph/agents.json +72 -0
  162. htmlgraph/hooks/.htmlgraph/index.sqlite +0 -0
  163. htmlgraph/hooks/__init__.py +45 -0
  164. htmlgraph/hooks/bootstrap.py +169 -0
  165. htmlgraph/hooks/cigs_pretool_enforcer.py +354 -0
  166. htmlgraph/hooks/concurrent_sessions.py +208 -0
  167. htmlgraph/hooks/context.py +350 -0
  168. htmlgraph/hooks/drift_handler.py +525 -0
  169. htmlgraph/hooks/event_tracker.py +1314 -0
  170. htmlgraph/hooks/git_commands.py +175 -0
  171. htmlgraph/hooks/hooks-config.example.json +12 -0
  172. htmlgraph/hooks/installer.py +343 -0
  173. htmlgraph/hooks/orchestrator.py +674 -0
  174. htmlgraph/hooks/orchestrator_reflector.py +223 -0
  175. htmlgraph/hooks/post-checkout.sh +28 -0
  176. htmlgraph/hooks/post-commit.sh +24 -0
  177. htmlgraph/hooks/post-merge.sh +26 -0
  178. htmlgraph/hooks/post_tool_use_failure.py +273 -0
  179. htmlgraph/hooks/post_tool_use_handler.py +257 -0
  180. htmlgraph/hooks/posttooluse.py +408 -0
  181. htmlgraph/hooks/pre-commit.sh +94 -0
  182. htmlgraph/hooks/pre-push.sh +28 -0
  183. htmlgraph/hooks/pretooluse.py +819 -0
  184. htmlgraph/hooks/prompt_analyzer.py +637 -0
  185. htmlgraph/hooks/session_handler.py +668 -0
  186. htmlgraph/hooks/session_summary.py +395 -0
  187. htmlgraph/hooks/state_manager.py +504 -0
  188. htmlgraph/hooks/subagent_detection.py +202 -0
  189. htmlgraph/hooks/subagent_stop.py +369 -0
  190. htmlgraph/hooks/task_enforcer.py +255 -0
  191. htmlgraph/hooks/task_validator.py +177 -0
  192. htmlgraph/hooks/validator.py +628 -0
  193. htmlgraph/ids.py +41 -27
  194. htmlgraph/index.d.ts +286 -0
  195. htmlgraph/learning.py +767 -0
  196. htmlgraph/mcp_server.py +69 -23
  197. htmlgraph/models.py +1586 -87
  198. htmlgraph/operations/README.md +62 -0
  199. htmlgraph/operations/__init__.py +79 -0
  200. htmlgraph/operations/analytics.py +339 -0
  201. htmlgraph/operations/bootstrap.py +289 -0
  202. htmlgraph/operations/events.py +244 -0
  203. htmlgraph/operations/fastapi_server.py +231 -0
  204. htmlgraph/operations/hooks.py +350 -0
  205. htmlgraph/operations/initialization.py +597 -0
  206. htmlgraph/operations/initialization.py.backup +228 -0
  207. htmlgraph/operations/server.py +303 -0
  208. htmlgraph/orchestration/__init__.py +58 -0
  209. htmlgraph/orchestration/claude_launcher.py +179 -0
  210. htmlgraph/orchestration/command_builder.py +72 -0
  211. htmlgraph/orchestration/headless_spawner.py +281 -0
  212. htmlgraph/orchestration/live_events.py +377 -0
  213. htmlgraph/orchestration/model_selection.py +327 -0
  214. htmlgraph/orchestration/plugin_manager.py +140 -0
  215. htmlgraph/orchestration/prompts.py +137 -0
  216. htmlgraph/orchestration/spawner_event_tracker.py +383 -0
  217. htmlgraph/orchestration/spawners/__init__.py +16 -0
  218. htmlgraph/orchestration/spawners/base.py +194 -0
  219. htmlgraph/orchestration/spawners/claude.py +173 -0
  220. htmlgraph/orchestration/spawners/codex.py +435 -0
  221. htmlgraph/orchestration/spawners/copilot.py +294 -0
  222. htmlgraph/orchestration/spawners/gemini.py +471 -0
  223. htmlgraph/orchestration/subprocess_runner.py +36 -0
  224. htmlgraph/orchestration/task_coordination.py +343 -0
  225. htmlgraph/orchestration.md +563 -0
  226. htmlgraph/orchestrator-system-prompt-optimized.txt +863 -0
  227. htmlgraph/orchestrator.py +669 -0
  228. htmlgraph/orchestrator_config.py +357 -0
  229. htmlgraph/orchestrator_mode.py +328 -0
  230. htmlgraph/orchestrator_validator.py +133 -0
  231. htmlgraph/parallel.py +646 -0
  232. htmlgraph/parser.py +160 -35
  233. htmlgraph/path_query.py +608 -0
  234. htmlgraph/pattern_matcher.py +636 -0
  235. htmlgraph/planning.py +147 -52
  236. htmlgraph/pydantic_models.py +476 -0
  237. htmlgraph/quality_gates.py +350 -0
  238. htmlgraph/query_builder.py +109 -72
  239. htmlgraph/query_composer.py +509 -0
  240. htmlgraph/reflection.py +443 -0
  241. htmlgraph/refs.py +344 -0
  242. htmlgraph/repo_hash.py +512 -0
  243. htmlgraph/repositories/__init__.py +292 -0
  244. htmlgraph/repositories/analytics_repository.py +455 -0
  245. htmlgraph/repositories/analytics_repository_standard.py +628 -0
  246. htmlgraph/repositories/feature_repository.py +581 -0
  247. htmlgraph/repositories/feature_repository_htmlfile.py +668 -0
  248. htmlgraph/repositories/feature_repository_memory.py +607 -0
  249. htmlgraph/repositories/feature_repository_sqlite.py +858 -0
  250. htmlgraph/repositories/filter_service.py +620 -0
  251. htmlgraph/repositories/filter_service_standard.py +445 -0
  252. htmlgraph/repositories/shared_cache.py +621 -0
  253. htmlgraph/repositories/shared_cache_memory.py +395 -0
  254. htmlgraph/repositories/track_repository.py +552 -0
  255. htmlgraph/repositories/track_repository_htmlfile.py +619 -0
  256. htmlgraph/repositories/track_repository_memory.py +508 -0
  257. htmlgraph/repositories/track_repository_sqlite.py +711 -0
  258. htmlgraph/routing.py +8 -19
  259. htmlgraph/scripts/deploy.py +1 -2
  260. htmlgraph/sdk/__init__.py +398 -0
  261. htmlgraph/sdk/__init__.pyi +14 -0
  262. htmlgraph/sdk/analytics/__init__.py +19 -0
  263. htmlgraph/sdk/analytics/engine.py +155 -0
  264. htmlgraph/sdk/analytics/helpers.py +178 -0
  265. htmlgraph/sdk/analytics/registry.py +109 -0
  266. htmlgraph/sdk/base.py +484 -0
  267. htmlgraph/sdk/constants.py +216 -0
  268. htmlgraph/sdk/core.pyi +308 -0
  269. htmlgraph/sdk/discovery.py +120 -0
  270. htmlgraph/sdk/help/__init__.py +12 -0
  271. htmlgraph/sdk/help/mixin.py +699 -0
  272. htmlgraph/sdk/mixins/__init__.py +15 -0
  273. htmlgraph/sdk/mixins/attribution.py +113 -0
  274. htmlgraph/sdk/mixins/mixin.py +410 -0
  275. htmlgraph/sdk/operations/__init__.py +12 -0
  276. htmlgraph/sdk/operations/mixin.py +427 -0
  277. htmlgraph/sdk/orchestration/__init__.py +17 -0
  278. htmlgraph/sdk/orchestration/coordinator.py +203 -0
  279. htmlgraph/sdk/orchestration/spawner.py +204 -0
  280. htmlgraph/sdk/planning/__init__.py +19 -0
  281. htmlgraph/sdk/planning/bottlenecks.py +93 -0
  282. htmlgraph/sdk/planning/mixin.py +211 -0
  283. htmlgraph/sdk/planning/parallel.py +186 -0
  284. htmlgraph/sdk/planning/queue.py +210 -0
  285. htmlgraph/sdk/planning/recommendations.py +87 -0
  286. htmlgraph/sdk/planning/smart_planning.py +319 -0
  287. htmlgraph/sdk/session/__init__.py +19 -0
  288. htmlgraph/sdk/session/continuity.py +57 -0
  289. htmlgraph/sdk/session/handoff.py +110 -0
  290. htmlgraph/sdk/session/info.py +309 -0
  291. htmlgraph/sdk/session/manager.py +103 -0
  292. htmlgraph/sdk/strategic/__init__.py +26 -0
  293. htmlgraph/sdk/strategic/mixin.py +563 -0
  294. htmlgraph/server.py +685 -180
  295. htmlgraph/services/__init__.py +10 -0
  296. htmlgraph/services/claiming.py +199 -0
  297. htmlgraph/session_hooks.py +300 -0
  298. htmlgraph/session_manager.py +1392 -175
  299. htmlgraph/session_registry.py +587 -0
  300. htmlgraph/session_state.py +436 -0
  301. htmlgraph/session_warning.py +201 -0
  302. htmlgraph/sessions/__init__.py +23 -0
  303. htmlgraph/sessions/handoff.py +756 -0
  304. htmlgraph/setup.py +34 -17
  305. htmlgraph/spike_index.py +143 -0
  306. htmlgraph/sync_docs.py +12 -15
  307. htmlgraph/system_prompts.py +450 -0
  308. htmlgraph/templates/AGENTS.md.template +366 -0
  309. htmlgraph/templates/CLAUDE.md.template +97 -0
  310. htmlgraph/templates/GEMINI.md.template +87 -0
  311. htmlgraph/templates/orchestration-view.html +350 -0
  312. htmlgraph/track_builder.py +146 -15
  313. htmlgraph/track_manager.py +69 -21
  314. htmlgraph/transcript.py +890 -0
  315. htmlgraph/transcript_analytics.py +699 -0
  316. htmlgraph/types.py +323 -0
  317. htmlgraph/validation.py +115 -0
  318. htmlgraph/watch.py +8 -5
  319. htmlgraph/work_type_utils.py +3 -2
  320. {htmlgraph-0.9.3.data → htmlgraph-0.27.5.data}/data/htmlgraph/dashboard.html +2406 -307
  321. htmlgraph-0.27.5.data/data/htmlgraph/templates/AGENTS.md.template +366 -0
  322. htmlgraph-0.27.5.data/data/htmlgraph/templates/CLAUDE.md.template +97 -0
  323. htmlgraph-0.27.5.data/data/htmlgraph/templates/GEMINI.md.template +87 -0
  324. {htmlgraph-0.9.3.dist-info → htmlgraph-0.27.5.dist-info}/METADATA +97 -64
  325. htmlgraph-0.27.5.dist-info/RECORD +337 -0
  326. {htmlgraph-0.9.3.dist-info → htmlgraph-0.27.5.dist-info}/entry_points.txt +1 -1
  327. htmlgraph/cli.py +0 -2688
  328. htmlgraph/sdk.py +0 -709
  329. htmlgraph-0.9.3.dist-info/RECORD +0 -61
  330. {htmlgraph-0.9.3.data → htmlgraph-0.27.5.data}/data/htmlgraph/styles.css +0 -0
  331. {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