htmlgraph 0.20.1__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 (304) 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 +51 -1
  5. htmlgraph/__init__.pyi +123 -0
  6. htmlgraph/agent_detection.py +26 -10
  7. htmlgraph/agent_registry.py +2 -1
  8. htmlgraph/analytics/__init__.py +8 -1
  9. htmlgraph/analytics/cli.py +86 -20
  10. htmlgraph/analytics/cost_analyzer.py +391 -0
  11. htmlgraph/analytics/cost_monitor.py +664 -0
  12. htmlgraph/analytics/cost_reporter.py +675 -0
  13. htmlgraph/analytics/cross_session.py +617 -0
  14. htmlgraph/analytics/dependency.py +10 -6
  15. htmlgraph/analytics/pattern_learning.py +771 -0
  16. htmlgraph/analytics/session_graph.py +707 -0
  17. htmlgraph/analytics/strategic/__init__.py +80 -0
  18. htmlgraph/analytics/strategic/cost_optimizer.py +611 -0
  19. htmlgraph/analytics/strategic/pattern_detector.py +876 -0
  20. htmlgraph/analytics/strategic/preference_manager.py +709 -0
  21. htmlgraph/analytics/strategic/suggestion_engine.py +747 -0
  22. htmlgraph/analytics/work_type.py +67 -27
  23. htmlgraph/analytics_index.py +53 -20
  24. htmlgraph/api/__init__.py +3 -0
  25. htmlgraph/api/cost_alerts_websocket.py +416 -0
  26. htmlgraph/api/main.py +2498 -0
  27. htmlgraph/api/static/htmx.min.js +1 -0
  28. htmlgraph/api/static/style-redesign.css +1344 -0
  29. htmlgraph/api/static/style.css +1079 -0
  30. htmlgraph/api/templates/dashboard-redesign.html +1366 -0
  31. htmlgraph/api/templates/dashboard.html +794 -0
  32. htmlgraph/api/templates/partials/activity-feed-hierarchical.html +326 -0
  33. htmlgraph/api/templates/partials/activity-feed.html +1100 -0
  34. htmlgraph/api/templates/partials/agents-redesign.html +317 -0
  35. htmlgraph/api/templates/partials/agents.html +317 -0
  36. htmlgraph/api/templates/partials/event-traces.html +373 -0
  37. htmlgraph/api/templates/partials/features-kanban-redesign.html +509 -0
  38. htmlgraph/api/templates/partials/features.html +578 -0
  39. htmlgraph/api/templates/partials/metrics-redesign.html +346 -0
  40. htmlgraph/api/templates/partials/metrics.html +346 -0
  41. htmlgraph/api/templates/partials/orchestration-redesign.html +443 -0
  42. htmlgraph/api/templates/partials/orchestration.html +198 -0
  43. htmlgraph/api/templates/partials/spawners.html +375 -0
  44. htmlgraph/api/templates/partials/work-items.html +613 -0
  45. htmlgraph/api/websocket.py +538 -0
  46. htmlgraph/archive/__init__.py +24 -0
  47. htmlgraph/archive/bloom.py +234 -0
  48. htmlgraph/archive/fts.py +297 -0
  49. htmlgraph/archive/manager.py +583 -0
  50. htmlgraph/archive/search.py +244 -0
  51. htmlgraph/atomic_ops.py +560 -0
  52. htmlgraph/attribute_index.py +2 -1
  53. htmlgraph/bounded_paths.py +539 -0
  54. htmlgraph/builders/base.py +57 -2
  55. htmlgraph/builders/bug.py +19 -3
  56. htmlgraph/builders/chore.py +19 -3
  57. htmlgraph/builders/epic.py +19 -3
  58. htmlgraph/builders/feature.py +27 -3
  59. htmlgraph/builders/insight.py +2 -1
  60. htmlgraph/builders/metric.py +2 -1
  61. htmlgraph/builders/pattern.py +2 -1
  62. htmlgraph/builders/phase.py +19 -3
  63. htmlgraph/builders/spike.py +29 -3
  64. htmlgraph/builders/track.py +42 -1
  65. htmlgraph/cigs/__init__.py +81 -0
  66. htmlgraph/cigs/autonomy.py +385 -0
  67. htmlgraph/cigs/cost.py +475 -0
  68. htmlgraph/cigs/messages_basic.py +472 -0
  69. htmlgraph/cigs/messaging.py +365 -0
  70. htmlgraph/cigs/models.py +771 -0
  71. htmlgraph/cigs/pattern_storage.py +427 -0
  72. htmlgraph/cigs/patterns.py +503 -0
  73. htmlgraph/cigs/posttool_analyzer.py +234 -0
  74. htmlgraph/cigs/reporter.py +818 -0
  75. htmlgraph/cigs/tracker.py +317 -0
  76. htmlgraph/cli/.htmlgraph/.session-warning-state.json +6 -0
  77. htmlgraph/cli/.htmlgraph/agents.json +72 -0
  78. htmlgraph/cli/.htmlgraph/htmlgraph.db +0 -0
  79. htmlgraph/cli/__init__.py +42 -0
  80. htmlgraph/cli/__main__.py +6 -0
  81. htmlgraph/cli/analytics.py +1424 -0
  82. htmlgraph/cli/base.py +685 -0
  83. htmlgraph/cli/constants.py +206 -0
  84. htmlgraph/cli/core.py +954 -0
  85. htmlgraph/cli/main.py +147 -0
  86. htmlgraph/cli/models.py +475 -0
  87. htmlgraph/cli/templates/__init__.py +1 -0
  88. htmlgraph/cli/templates/cost_dashboard.py +399 -0
  89. htmlgraph/cli/work/__init__.py +239 -0
  90. htmlgraph/cli/work/browse.py +115 -0
  91. htmlgraph/cli/work/features.py +568 -0
  92. htmlgraph/cli/work/orchestration.py +676 -0
  93. htmlgraph/cli/work/report.py +728 -0
  94. htmlgraph/cli/work/sessions.py +466 -0
  95. htmlgraph/cli/work/snapshot.py +559 -0
  96. htmlgraph/cli/work/tracks.py +486 -0
  97. htmlgraph/cli_commands/__init__.py +1 -0
  98. htmlgraph/cli_commands/feature.py +195 -0
  99. htmlgraph/cli_framework.py +115 -0
  100. htmlgraph/collections/__init__.py +2 -0
  101. htmlgraph/collections/base.py +197 -14
  102. htmlgraph/collections/bug.py +2 -1
  103. htmlgraph/collections/chore.py +2 -1
  104. htmlgraph/collections/epic.py +2 -1
  105. htmlgraph/collections/feature.py +2 -1
  106. htmlgraph/collections/insight.py +2 -1
  107. htmlgraph/collections/metric.py +2 -1
  108. htmlgraph/collections/pattern.py +2 -1
  109. htmlgraph/collections/phase.py +2 -1
  110. htmlgraph/collections/session.py +194 -0
  111. htmlgraph/collections/spike.py +13 -2
  112. htmlgraph/collections/task_delegation.py +241 -0
  113. htmlgraph/collections/todo.py +14 -1
  114. htmlgraph/collections/traces.py +487 -0
  115. htmlgraph/config/cost_models.json +56 -0
  116. htmlgraph/config.py +190 -0
  117. htmlgraph/context_analytics.py +2 -1
  118. htmlgraph/converter.py +116 -7
  119. htmlgraph/cost_analysis/__init__.py +5 -0
  120. htmlgraph/cost_analysis/analyzer.py +438 -0
  121. htmlgraph/dashboard.html +2246 -248
  122. htmlgraph/dashboard.html.backup +6592 -0
  123. htmlgraph/dashboard.html.bak +7181 -0
  124. htmlgraph/dashboard.html.bak2 +7231 -0
  125. htmlgraph/dashboard.html.bak3 +7232 -0
  126. htmlgraph/db/__init__.py +38 -0
  127. htmlgraph/db/queries.py +790 -0
  128. htmlgraph/db/schema.py +1788 -0
  129. htmlgraph/decorators.py +317 -0
  130. htmlgraph/dependency_models.py +2 -1
  131. htmlgraph/deploy.py +26 -27
  132. htmlgraph/docs/API_REFERENCE.md +841 -0
  133. htmlgraph/docs/HTTP_API.md +750 -0
  134. htmlgraph/docs/INTEGRATION_GUIDE.md +752 -0
  135. htmlgraph/docs/ORCHESTRATION_PATTERNS.md +717 -0
  136. htmlgraph/docs/README.md +532 -0
  137. htmlgraph/docs/__init__.py +77 -0
  138. htmlgraph/docs/docs_version.py +55 -0
  139. htmlgraph/docs/metadata.py +93 -0
  140. htmlgraph/docs/migrations.py +232 -0
  141. htmlgraph/docs/template_engine.py +143 -0
  142. htmlgraph/docs/templates/_sections/cli_reference.md.j2 +52 -0
  143. htmlgraph/docs/templates/_sections/core_concepts.md.j2 +29 -0
  144. htmlgraph/docs/templates/_sections/sdk_basics.md.j2 +69 -0
  145. htmlgraph/docs/templates/base_agents.md.j2 +78 -0
  146. htmlgraph/docs/templates/example_user_override.md.j2 +47 -0
  147. htmlgraph/docs/version_check.py +163 -0
  148. htmlgraph/edge_index.py +2 -1
  149. htmlgraph/error_handler.py +544 -0
  150. htmlgraph/event_log.py +86 -37
  151. htmlgraph/event_migration.py +2 -1
  152. htmlgraph/file_watcher.py +12 -8
  153. htmlgraph/find_api.py +2 -1
  154. htmlgraph/git_events.py +67 -9
  155. htmlgraph/hooks/.htmlgraph/.session-warning-state.json +6 -0
  156. htmlgraph/hooks/.htmlgraph/agents.json +72 -0
  157. htmlgraph/hooks/.htmlgraph/index.sqlite +0 -0
  158. htmlgraph/hooks/__init__.py +8 -0
  159. htmlgraph/hooks/bootstrap.py +169 -0
  160. htmlgraph/hooks/cigs_pretool_enforcer.py +354 -0
  161. htmlgraph/hooks/concurrent_sessions.py +208 -0
  162. htmlgraph/hooks/context.py +350 -0
  163. htmlgraph/hooks/drift_handler.py +525 -0
  164. htmlgraph/hooks/event_tracker.py +790 -99
  165. htmlgraph/hooks/git_commands.py +175 -0
  166. htmlgraph/hooks/installer.py +5 -1
  167. htmlgraph/hooks/orchestrator.py +327 -76
  168. htmlgraph/hooks/orchestrator_reflector.py +31 -4
  169. htmlgraph/hooks/post_tool_use_failure.py +32 -7
  170. htmlgraph/hooks/post_tool_use_handler.py +257 -0
  171. htmlgraph/hooks/posttooluse.py +92 -19
  172. htmlgraph/hooks/pretooluse.py +527 -7
  173. htmlgraph/hooks/prompt_analyzer.py +637 -0
  174. htmlgraph/hooks/session_handler.py +668 -0
  175. htmlgraph/hooks/session_summary.py +395 -0
  176. htmlgraph/hooks/state_manager.py +504 -0
  177. htmlgraph/hooks/subagent_detection.py +202 -0
  178. htmlgraph/hooks/subagent_stop.py +369 -0
  179. htmlgraph/hooks/task_enforcer.py +99 -4
  180. htmlgraph/hooks/validator.py +212 -91
  181. htmlgraph/ids.py +2 -1
  182. htmlgraph/learning.py +125 -100
  183. htmlgraph/mcp_server.py +2 -1
  184. htmlgraph/models.py +217 -18
  185. htmlgraph/operations/README.md +62 -0
  186. htmlgraph/operations/__init__.py +79 -0
  187. htmlgraph/operations/analytics.py +339 -0
  188. htmlgraph/operations/bootstrap.py +289 -0
  189. htmlgraph/operations/events.py +244 -0
  190. htmlgraph/operations/fastapi_server.py +231 -0
  191. htmlgraph/operations/hooks.py +350 -0
  192. htmlgraph/operations/initialization.py +597 -0
  193. htmlgraph/operations/initialization.py.backup +228 -0
  194. htmlgraph/operations/server.py +303 -0
  195. htmlgraph/orchestration/__init__.py +58 -0
  196. htmlgraph/orchestration/claude_launcher.py +179 -0
  197. htmlgraph/orchestration/command_builder.py +72 -0
  198. htmlgraph/orchestration/headless_spawner.py +281 -0
  199. htmlgraph/orchestration/live_events.py +377 -0
  200. htmlgraph/orchestration/model_selection.py +327 -0
  201. htmlgraph/orchestration/plugin_manager.py +140 -0
  202. htmlgraph/orchestration/prompts.py +137 -0
  203. htmlgraph/orchestration/spawner_event_tracker.py +383 -0
  204. htmlgraph/orchestration/spawners/__init__.py +16 -0
  205. htmlgraph/orchestration/spawners/base.py +194 -0
  206. htmlgraph/orchestration/spawners/claude.py +173 -0
  207. htmlgraph/orchestration/spawners/codex.py +435 -0
  208. htmlgraph/orchestration/spawners/copilot.py +294 -0
  209. htmlgraph/orchestration/spawners/gemini.py +471 -0
  210. htmlgraph/orchestration/subprocess_runner.py +36 -0
  211. htmlgraph/{orchestration.py → orchestration/task_coordination.py} +16 -8
  212. htmlgraph/orchestration.md +563 -0
  213. htmlgraph/orchestrator-system-prompt-optimized.txt +863 -0
  214. htmlgraph/orchestrator.py +2 -1
  215. htmlgraph/orchestrator_config.py +357 -0
  216. htmlgraph/orchestrator_mode.py +115 -4
  217. htmlgraph/parallel.py +2 -1
  218. htmlgraph/parser.py +86 -6
  219. htmlgraph/path_query.py +608 -0
  220. htmlgraph/pattern_matcher.py +636 -0
  221. htmlgraph/pydantic_models.py +476 -0
  222. htmlgraph/quality_gates.py +350 -0
  223. htmlgraph/query_builder.py +2 -1
  224. htmlgraph/query_composer.py +509 -0
  225. htmlgraph/reflection.py +443 -0
  226. htmlgraph/refs.py +344 -0
  227. htmlgraph/repo_hash.py +512 -0
  228. htmlgraph/repositories/__init__.py +292 -0
  229. htmlgraph/repositories/analytics_repository.py +455 -0
  230. htmlgraph/repositories/analytics_repository_standard.py +628 -0
  231. htmlgraph/repositories/feature_repository.py +581 -0
  232. htmlgraph/repositories/feature_repository_htmlfile.py +668 -0
  233. htmlgraph/repositories/feature_repository_memory.py +607 -0
  234. htmlgraph/repositories/feature_repository_sqlite.py +858 -0
  235. htmlgraph/repositories/filter_service.py +620 -0
  236. htmlgraph/repositories/filter_service_standard.py +445 -0
  237. htmlgraph/repositories/shared_cache.py +621 -0
  238. htmlgraph/repositories/shared_cache_memory.py +395 -0
  239. htmlgraph/repositories/track_repository.py +552 -0
  240. htmlgraph/repositories/track_repository_htmlfile.py +619 -0
  241. htmlgraph/repositories/track_repository_memory.py +508 -0
  242. htmlgraph/repositories/track_repository_sqlite.py +711 -0
  243. htmlgraph/sdk/__init__.py +398 -0
  244. htmlgraph/sdk/__init__.pyi +14 -0
  245. htmlgraph/sdk/analytics/__init__.py +19 -0
  246. htmlgraph/sdk/analytics/engine.py +155 -0
  247. htmlgraph/sdk/analytics/helpers.py +178 -0
  248. htmlgraph/sdk/analytics/registry.py +109 -0
  249. htmlgraph/sdk/base.py +484 -0
  250. htmlgraph/sdk/constants.py +216 -0
  251. htmlgraph/sdk/core.pyi +308 -0
  252. htmlgraph/sdk/discovery.py +120 -0
  253. htmlgraph/sdk/help/__init__.py +12 -0
  254. htmlgraph/sdk/help/mixin.py +699 -0
  255. htmlgraph/sdk/mixins/__init__.py +15 -0
  256. htmlgraph/sdk/mixins/attribution.py +113 -0
  257. htmlgraph/sdk/mixins/mixin.py +410 -0
  258. htmlgraph/sdk/operations/__init__.py +12 -0
  259. htmlgraph/sdk/operations/mixin.py +427 -0
  260. htmlgraph/sdk/orchestration/__init__.py +17 -0
  261. htmlgraph/sdk/orchestration/coordinator.py +203 -0
  262. htmlgraph/sdk/orchestration/spawner.py +204 -0
  263. htmlgraph/sdk/planning/__init__.py +19 -0
  264. htmlgraph/sdk/planning/bottlenecks.py +93 -0
  265. htmlgraph/sdk/planning/mixin.py +211 -0
  266. htmlgraph/sdk/planning/parallel.py +186 -0
  267. htmlgraph/sdk/planning/queue.py +210 -0
  268. htmlgraph/sdk/planning/recommendations.py +87 -0
  269. htmlgraph/sdk/planning/smart_planning.py +319 -0
  270. htmlgraph/sdk/session/__init__.py +19 -0
  271. htmlgraph/sdk/session/continuity.py +57 -0
  272. htmlgraph/sdk/session/handoff.py +110 -0
  273. htmlgraph/sdk/session/info.py +309 -0
  274. htmlgraph/sdk/session/manager.py +103 -0
  275. htmlgraph/sdk/strategic/__init__.py +26 -0
  276. htmlgraph/sdk/strategic/mixin.py +563 -0
  277. htmlgraph/server.py +295 -107
  278. htmlgraph/session_hooks.py +300 -0
  279. htmlgraph/session_manager.py +285 -3
  280. htmlgraph/session_registry.py +587 -0
  281. htmlgraph/session_state.py +436 -0
  282. htmlgraph/session_warning.py +2 -1
  283. htmlgraph/sessions/__init__.py +23 -0
  284. htmlgraph/sessions/handoff.py +756 -0
  285. htmlgraph/system_prompts.py +450 -0
  286. htmlgraph/templates/orchestration-view.html +350 -0
  287. htmlgraph/track_builder.py +33 -1
  288. htmlgraph/track_manager.py +38 -0
  289. htmlgraph/transcript.py +18 -5
  290. htmlgraph/validation.py +115 -0
  291. htmlgraph/watch.py +2 -1
  292. htmlgraph/work_type_utils.py +2 -1
  293. {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/dashboard.html +2246 -248
  294. {htmlgraph-0.20.1.dist-info → htmlgraph-0.27.5.dist-info}/METADATA +95 -64
  295. htmlgraph-0.27.5.dist-info/RECORD +337 -0
  296. {htmlgraph-0.20.1.dist-info → htmlgraph-0.27.5.dist-info}/entry_points.txt +1 -1
  297. htmlgraph/cli.py +0 -4839
  298. htmlgraph/sdk.py +0 -2359
  299. htmlgraph-0.20.1.dist-info/RECORD +0 -118
  300. {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/styles.css +0 -0
  301. {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/templates/AGENTS.md.template +0 -0
  302. {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/templates/CLAUDE.md.template +0 -0
  303. {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/templates/GEMINI.md.template +0 -0
  304. {htmlgraph-0.20.1.dist-info → htmlgraph-0.27.5.dist-info}/WHEEL +0 -0
@@ -0,0 +1,80 @@
1
+ """
2
+ Strategic Analytics - Phase 3: Pattern Detection & Smart Suggestions
3
+
4
+ This module provides intelligent pattern detection and suggestion systems
5
+ that learn from user delegation patterns and make smart suggestions.
6
+
7
+ Key Components:
8
+ 1. PatternDetector - Detects tool sequences, delegation chains, error patterns
9
+ 2. SuggestionEngine - Generates context-aware suggestions with ranking
10
+ 3. PreferenceManager - Learns user preferences from feedback
11
+ 4. CostOptimizer - Suggests token budgets, parallelization, model selection
12
+
13
+ Usage:
14
+ from htmlgraph.analytics.strategic import (
15
+ PatternDetector,
16
+ SuggestionEngine,
17
+ PreferenceManager,
18
+ CostOptimizer,
19
+ )
20
+
21
+ # Detect patterns from event history
22
+ detector = PatternDetector(db_path)
23
+ patterns = detector.detect_all_patterns()
24
+
25
+ # Get suggestions for current context
26
+ engine = SuggestionEngine(db_path)
27
+ suggestions = engine.suggest(context)
28
+
29
+ # Learn from feedback
30
+ manager = PreferenceManager(db_path)
31
+ manager.record_feedback(suggestion_id, accepted=True)
32
+ """
33
+
34
+ from htmlgraph.analytics.strategic.cost_optimizer import (
35
+ CostOptimizer,
36
+ ModelRecommendation,
37
+ ParallelizationStrategy,
38
+ TokenBudget,
39
+ )
40
+ from htmlgraph.analytics.strategic.pattern_detector import (
41
+ DelegationChain,
42
+ ErrorPattern,
43
+ Pattern,
44
+ PatternDetector,
45
+ PatternType,
46
+ ToolSequencePattern,
47
+ )
48
+ from htmlgraph.analytics.strategic.preference_manager import (
49
+ Feedback,
50
+ PreferenceManager,
51
+ UserPreferences,
52
+ )
53
+ from htmlgraph.analytics.strategic.suggestion_engine import (
54
+ Suggestion,
55
+ SuggestionEngine,
56
+ SuggestionType,
57
+ )
58
+
59
+ __all__ = [
60
+ # Pattern Detection
61
+ "PatternDetector",
62
+ "Pattern",
63
+ "PatternType",
64
+ "ToolSequencePattern",
65
+ "DelegationChain",
66
+ "ErrorPattern",
67
+ # Suggestion Engine
68
+ "SuggestionEngine",
69
+ "Suggestion",
70
+ "SuggestionType",
71
+ # Preference Manager
72
+ "PreferenceManager",
73
+ "UserPreferences",
74
+ "Feedback",
75
+ # Cost Optimizer
76
+ "CostOptimizer",
77
+ "TokenBudget",
78
+ "ParallelizationStrategy",
79
+ "ModelRecommendation",
80
+ ]
@@ -0,0 +1,611 @@
1
+ """
2
+ CostOptimizer - Suggests token budgets, parallelization, and model selection.
3
+
4
+ This module provides cost optimization recommendations:
5
+ 1. Token budgeting - Automatically suggest token budgets based on task scope
6
+ 2. Parallelization - Calculate optimal parallelization strategies
7
+ 3. Model selection - Choose cheapest model that can handle task
8
+ 4. Caching - Identify caching opportunities to save tokens
9
+
10
+ Usage:
11
+ from htmlgraph.analytics.strategic import CostOptimizer
12
+
13
+ optimizer = CostOptimizer(db_path)
14
+
15
+ # Get token budget suggestion
16
+ budget = optimizer.suggest_token_budget(task_description)
17
+
18
+ # Get parallelization strategy
19
+ strategy = optimizer.suggest_parallelization(tasks)
20
+
21
+ # Get model recommendation
22
+ model = optimizer.choose_model(task, preferences)
23
+ """
24
+
25
+ import logging
26
+ import sqlite3
27
+ from dataclasses import dataclass, field
28
+ from enum import Enum
29
+ from pathlib import Path
30
+ from typing import Any
31
+
32
+ logger = logging.getLogger(__name__)
33
+
34
+
35
+ class ModelTier(Enum):
36
+ """Model tiers by capability and cost."""
37
+
38
+ HAIKU = "haiku" # Fast, cheap, simple tasks
39
+ SONNET = "sonnet" # Balanced, default choice
40
+ OPUS = "opus" # Complex, expensive, high-quality
41
+
42
+
43
+ @dataclass
44
+ class TokenBudget:
45
+ """
46
+ Token budget recommendation for a task.
47
+
48
+ Attributes:
49
+ recommended: Recommended token budget
50
+ minimum: Minimum viable budget
51
+ maximum: Maximum reasonable budget
52
+ confidence: Confidence in the recommendation
53
+ reasoning: Explanation for the recommendation
54
+ based_on_history: Whether based on historical data
55
+ """
56
+
57
+ recommended: int
58
+ minimum: int
59
+ maximum: int
60
+ confidence: float = 0.7
61
+ reasoning: str = ""
62
+ based_on_history: bool = False
63
+
64
+ def to_dict(self) -> dict[str, Any]:
65
+ """Convert to dictionary."""
66
+ return {
67
+ "recommended": self.recommended,
68
+ "minimum": self.minimum,
69
+ "maximum": self.maximum,
70
+ "confidence": self.confidence,
71
+ "reasoning": self.reasoning,
72
+ "based_on_history": self.based_on_history,
73
+ }
74
+
75
+
76
+ @dataclass
77
+ class ParallelizationStrategy:
78
+ """
79
+ Parallelization strategy for a set of tasks.
80
+
81
+ Attributes:
82
+ groups: List of task groups that can run in parallel
83
+ estimated_speedup: Expected speedup factor
84
+ estimated_cost: Estimated total token cost
85
+ reasoning: Explanation for the strategy
86
+ """
87
+
88
+ groups: list[list[str]] = field(default_factory=list)
89
+ estimated_speedup: float = 1.0
90
+ estimated_cost: int = 0
91
+ reasoning: str = ""
92
+
93
+ def to_dict(self) -> dict[str, Any]:
94
+ """Convert to dictionary."""
95
+ return {
96
+ "groups": self.groups,
97
+ "estimated_speedup": self.estimated_speedup,
98
+ "estimated_cost": self.estimated_cost,
99
+ "reasoning": self.reasoning,
100
+ }
101
+
102
+
103
+ @dataclass
104
+ class ModelRecommendation:
105
+ """
106
+ Model selection recommendation.
107
+
108
+ Attributes:
109
+ recommended_model: Recommended model tier
110
+ alternative_model: Alternative if budget constrained
111
+ confidence: Confidence in recommendation
112
+ reasoning: Explanation for the choice
113
+ estimated_cost: Estimated token cost with this model
114
+ estimated_quality: Expected quality score (0-1)
115
+ """
116
+
117
+ recommended_model: ModelTier
118
+ alternative_model: ModelTier | None = None
119
+ confidence: float = 0.7
120
+ reasoning: str = ""
121
+ estimated_cost: int = 0
122
+ estimated_quality: float = 0.8
123
+
124
+ def to_dict(self) -> dict[str, Any]:
125
+ """Convert to dictionary."""
126
+ return {
127
+ "recommended_model": self.recommended_model.value,
128
+ "alternative_model": self.alternative_model.value
129
+ if self.alternative_model
130
+ else None,
131
+ "confidence": self.confidence,
132
+ "reasoning": self.reasoning,
133
+ "estimated_cost": self.estimated_cost,
134
+ "estimated_quality": self.estimated_quality,
135
+ }
136
+
137
+
138
+ class CostOptimizer:
139
+ """
140
+ Optimizes cost through token budgeting, parallelization, and model selection.
141
+
142
+ Uses historical data and heuristics to make intelligent cost decisions.
143
+ """
144
+
145
+ # Token cost multipliers (relative to Haiku = 1.0)
146
+ MODEL_COST_MULTIPLIERS = {
147
+ ModelTier.HAIKU: 1.0,
148
+ ModelTier.SONNET: 3.0,
149
+ ModelTier.OPUS: 15.0,
150
+ }
151
+
152
+ # Default token budgets by task complexity
153
+ DEFAULT_BUDGETS = {
154
+ "simple": 2000,
155
+ "medium": 5000,
156
+ "complex": 10000,
157
+ "very_complex": 20000,
158
+ }
159
+
160
+ def __init__(self, db_path: Path | str | None = None):
161
+ """
162
+ Initialize cost optimizer.
163
+
164
+ Args:
165
+ db_path: Path to HtmlGraph database. If None, uses default location.
166
+ """
167
+ if db_path is None:
168
+ from htmlgraph.config import get_database_path
169
+
170
+ db_path = get_database_path()
171
+
172
+ self.db_path = Path(db_path)
173
+ self._conn: sqlite3.Connection | None = None
174
+
175
+ def _get_connection(self) -> sqlite3.Connection:
176
+ """Get database connection with row factory."""
177
+ if self._conn is None:
178
+ self._conn = sqlite3.connect(str(self.db_path))
179
+ self._conn.row_factory = sqlite3.Row
180
+ return self._conn
181
+
182
+ def close(self) -> None:
183
+ """Close database connection."""
184
+ if self._conn:
185
+ self._conn.close()
186
+ self._conn = None
187
+
188
+ def suggest_token_budget(
189
+ self,
190
+ task_description: str,
191
+ tool_name: str | None = None,
192
+ ) -> TokenBudget:
193
+ """
194
+ Suggest token budget based on task description and history.
195
+
196
+ Args:
197
+ task_description: Description of the task
198
+ tool_name: Specific tool being used (optional)
199
+
200
+ Returns:
201
+ TokenBudget recommendation
202
+ """
203
+ # First, try to get historical data for similar tasks
204
+ historical = self._get_historical_token_usage(task_description, tool_name)
205
+
206
+ if historical:
207
+ return TokenBudget(
208
+ recommended=int(historical["avg_tokens"] * 1.2), # 20% buffer
209
+ minimum=int(historical["min_tokens"]),
210
+ maximum=int(historical["max_tokens"] * 1.1),
211
+ confidence=0.8,
212
+ reasoning=f"Based on {historical['count']} similar tasks",
213
+ based_on_history=True,
214
+ )
215
+
216
+ # Fall back to heuristics
217
+ complexity = self._estimate_complexity(task_description)
218
+ base_budget = self.DEFAULT_BUDGETS.get(complexity, 5000)
219
+
220
+ # Adjust for specific tools
221
+ if tool_name:
222
+ tool_multipliers = {
223
+ "Edit": 1.2, # Edits often need more context
224
+ "Write": 1.3, # Writes can be lengthy
225
+ "Bash": 0.8, # Bash commands typically shorter
226
+ "Read": 0.5, # Reads are cheap
227
+ "Grep": 0.3, # Grep is very cheap
228
+ "Task": 2.0, # Task delegations need more budget
229
+ }
230
+ multiplier = tool_multipliers.get(tool_name, 1.0)
231
+ base_budget = int(base_budget * multiplier)
232
+
233
+ return TokenBudget(
234
+ recommended=base_budget,
235
+ minimum=int(base_budget * 0.5),
236
+ maximum=int(base_budget * 2.0),
237
+ confidence=0.6,
238
+ reasoning=f"Estimated {complexity} complexity task",
239
+ based_on_history=False,
240
+ )
241
+
242
+ def _get_historical_token_usage(
243
+ self,
244
+ task_description: str,
245
+ tool_name: str | None = None,
246
+ ) -> dict[str, Any] | None:
247
+ """
248
+ Get historical token usage for similar tasks.
249
+
250
+ Args:
251
+ task_description: Task description to match
252
+ tool_name: Optional tool filter
253
+
254
+ Returns:
255
+ Dict with avg, min, max tokens and count, or None if insufficient data
256
+ """
257
+ conn = self._get_connection()
258
+ cursor = conn.cursor()
259
+
260
+ try:
261
+ # Build query based on available filters
262
+ if tool_name:
263
+ cursor.execute(
264
+ """
265
+ SELECT
266
+ AVG(cost_tokens) as avg_tokens,
267
+ MIN(cost_tokens) as min_tokens,
268
+ MAX(cost_tokens) as max_tokens,
269
+ COUNT(*) as count
270
+ FROM agent_events
271
+ WHERE tool_name = ?
272
+ AND cost_tokens > 0
273
+ AND timestamp > datetime('now', '-30 days')
274
+ """,
275
+ (tool_name,),
276
+ )
277
+ else:
278
+ cursor.execute(
279
+ """
280
+ SELECT
281
+ AVG(cost_tokens) as avg_tokens,
282
+ MIN(cost_tokens) as min_tokens,
283
+ MAX(cost_tokens) as max_tokens,
284
+ COUNT(*) as count
285
+ FROM agent_events
286
+ WHERE cost_tokens > 0
287
+ AND timestamp > datetime('now', '-30 days')
288
+ """
289
+ )
290
+
291
+ row = cursor.fetchone()
292
+ if row and row["count"] >= 5: # Need at least 5 samples
293
+ return {
294
+ "avg_tokens": row["avg_tokens"],
295
+ "min_tokens": row["min_tokens"],
296
+ "max_tokens": row["max_tokens"],
297
+ "count": row["count"],
298
+ }
299
+
300
+ return None
301
+
302
+ except sqlite3.Error as e:
303
+ logger.warning(f"Error getting historical token usage: {e}")
304
+ return None
305
+
306
+ def _estimate_complexity(self, task_description: str) -> str:
307
+ """
308
+ Estimate task complexity from description.
309
+
310
+ Args:
311
+ task_description: Task description
312
+
313
+ Returns:
314
+ Complexity level: simple, medium, complex, very_complex
315
+ """
316
+ task_lower = task_description.lower()
317
+
318
+ # Very complex indicators
319
+ very_complex_indicators = [
320
+ "refactor",
321
+ "architecture",
322
+ "redesign",
323
+ "migrate",
324
+ "rewrite",
325
+ "security audit",
326
+ "performance optimize",
327
+ ]
328
+ if any(ind in task_lower for ind in very_complex_indicators):
329
+ return "very_complex"
330
+
331
+ # Complex indicators
332
+ complex_indicators = [
333
+ "implement",
334
+ "create",
335
+ "build",
336
+ "design",
337
+ "integrate",
338
+ "debug",
339
+ "analyze",
340
+ "complex",
341
+ ]
342
+ if any(ind in task_lower for ind in complex_indicators):
343
+ return "complex"
344
+
345
+ # Simple indicators
346
+ simple_indicators = [
347
+ "fix typo",
348
+ "rename",
349
+ "format",
350
+ "lint",
351
+ "move",
352
+ "copy",
353
+ "delete",
354
+ "list",
355
+ "show",
356
+ "status",
357
+ ]
358
+ if any(ind in task_lower for ind in simple_indicators):
359
+ return "simple"
360
+
361
+ # Default to medium
362
+ return "medium"
363
+
364
+ def suggest_parallelization(
365
+ self,
366
+ tasks: list[dict[str, Any]],
367
+ ) -> ParallelizationStrategy:
368
+ """
369
+ Calculate optimal parallelization strategy for a set of tasks.
370
+
371
+ Analyzes task dependencies and groups independent tasks for parallel execution.
372
+
373
+ Args:
374
+ tasks: List of task dictionaries with 'id', 'description', 'dependencies'
375
+
376
+ Returns:
377
+ ParallelizationStrategy with grouped tasks
378
+ """
379
+ if not tasks:
380
+ return ParallelizationStrategy(
381
+ groups=[],
382
+ estimated_speedup=1.0,
383
+ reasoning="No tasks provided",
384
+ )
385
+
386
+ # Build dependency graph
387
+ task_ids = {t.get("id", str(i)): t for i, t in enumerate(tasks)}
388
+ dependencies: dict[str, set[str]] = {}
389
+
390
+ for task in tasks:
391
+ task_id = task.get("id", str(tasks.index(task)))
392
+ deps = set(task.get("dependencies", []))
393
+ dependencies[task_id] = deps
394
+
395
+ # Topological sort into parallel groups
396
+ groups: list[list[str]] = []
397
+ remaining = set(task_ids.keys())
398
+ completed: set[str] = set()
399
+
400
+ while remaining:
401
+ # Find tasks with all dependencies satisfied
402
+ ready = []
403
+ for task_id in remaining:
404
+ if dependencies[task_id].issubset(completed):
405
+ ready.append(task_id)
406
+
407
+ if not ready:
408
+ # Circular dependency - just add remaining tasks
409
+ groups.append(list(remaining))
410
+ break
411
+
412
+ groups.append(ready)
413
+ completed.update(ready)
414
+ remaining -= set(ready)
415
+
416
+ # Calculate estimated speedup
417
+ sequential_time = len(tasks)
418
+ parallel_time = len(groups)
419
+ speedup = sequential_time / parallel_time if parallel_time > 0 else 1.0
420
+
421
+ # Estimate cost (sum of individual task budgets)
422
+ total_cost = 0
423
+ for task in tasks:
424
+ desc = task.get("description", "")
425
+ budget = self.suggest_token_budget(desc)
426
+ total_cost += budget.recommended
427
+
428
+ return ParallelizationStrategy(
429
+ groups=groups,
430
+ estimated_speedup=speedup,
431
+ estimated_cost=total_cost,
432
+ reasoning=f"Grouped {len(tasks)} tasks into {len(groups)} parallel batches",
433
+ )
434
+
435
+ def choose_model(
436
+ self,
437
+ task_description: str,
438
+ budget_constraint: int | None = None,
439
+ quality_threshold: float = 0.7,
440
+ ) -> ModelRecommendation:
441
+ """
442
+ Choose the most cost-effective model for a task.
443
+
444
+ Balances cost, quality, and task requirements.
445
+
446
+ Args:
447
+ task_description: Description of the task
448
+ budget_constraint: Maximum token budget (optional)
449
+ quality_threshold: Minimum acceptable quality (0-1)
450
+
451
+ Returns:
452
+ ModelRecommendation with suggested model
453
+ """
454
+ complexity = self._estimate_complexity(task_description)
455
+
456
+ # Map complexity to model tier
457
+ complexity_model_map = {
458
+ "simple": (ModelTier.HAIKU, 0.7),
459
+ "medium": (ModelTier.SONNET, 0.85),
460
+ "complex": (ModelTier.SONNET, 0.9),
461
+ "very_complex": (ModelTier.OPUS, 0.95),
462
+ }
463
+
464
+ recommended, expected_quality = complexity_model_map.get(
465
+ complexity, (ModelTier.SONNET, 0.85)
466
+ )
467
+
468
+ # Check if we can use a cheaper model
469
+ alternative = None
470
+ if recommended == ModelTier.OPUS and quality_threshold <= 0.9:
471
+ alternative = ModelTier.SONNET
472
+ elif recommended == ModelTier.SONNET and quality_threshold <= 0.7:
473
+ alternative = ModelTier.HAIKU
474
+
475
+ # Calculate estimated cost
476
+ base_budget = self.DEFAULT_BUDGETS.get(complexity, 5000)
477
+ cost_multiplier = self.MODEL_COST_MULTIPLIERS[recommended]
478
+ estimated_cost = int(base_budget * cost_multiplier)
479
+
480
+ # Check budget constraint
481
+ if budget_constraint and estimated_cost > budget_constraint:
482
+ # Try to fit within budget with cheaper model
483
+ if alternative:
484
+ alt_cost = int(base_budget * self.MODEL_COST_MULTIPLIERS[alternative])
485
+ if alt_cost <= budget_constraint:
486
+ recommended = alternative
487
+ estimated_cost = alt_cost
488
+ expected_quality = expected_quality * 0.85
489
+
490
+ reasoning = (
491
+ f"{complexity.replace('_', ' ').title()} task - "
492
+ f"{recommended.value} recommended for balance of cost and quality"
493
+ )
494
+
495
+ return ModelRecommendation(
496
+ recommended_model=recommended,
497
+ alternative_model=alternative,
498
+ confidence=0.75,
499
+ reasoning=reasoning,
500
+ estimated_cost=estimated_cost,
501
+ estimated_quality=expected_quality,
502
+ )
503
+
504
+ def identify_cache_opportunities(
505
+ self,
506
+ recent_tools: list[dict[str, Any]],
507
+ ) -> list[dict[str, Any]]:
508
+ """
509
+ Identify caching opportunities to save tokens.
510
+
511
+ Looks for repeated patterns that could benefit from caching.
512
+
513
+ Args:
514
+ recent_tools: List of recent tool calls with input/output
515
+
516
+ Returns:
517
+ List of cache opportunity recommendations
518
+ """
519
+ opportunities: list[dict[str, Any]] = []
520
+
521
+ if len(recent_tools) < 3:
522
+ return opportunities
523
+
524
+ # Track repeated tool+input combinations
525
+ seen: dict[str, list[int]] = {}
526
+
527
+ for i, tool in enumerate(recent_tools):
528
+ tool_name = tool.get("tool_name", "")
529
+ # Create a simple key from tool name and input hash
530
+ input_key = str(tool.get("input", {}))[:100]
531
+ key = f"{tool_name}:{hash(input_key)}"
532
+
533
+ if key not in seen:
534
+ seen[key] = []
535
+ seen[key].append(i)
536
+
537
+ # Find repeated calls
538
+ for key, indices in seen.items():
539
+ if len(indices) >= 2:
540
+ tool_name = key.split(":")[0]
541
+ opportunities.append(
542
+ {
543
+ "tool_name": tool_name,
544
+ "occurrences": len(indices),
545
+ "suggestion": f"Cache {tool_name} results - called {len(indices)} times with same input",
546
+ "estimated_savings": len(indices) - 1, # Could save N-1 calls
547
+ }
548
+ )
549
+
550
+ return opportunities
551
+
552
+ def get_cost_summary(
553
+ self,
554
+ session_id: str,
555
+ ) -> dict[str, Any]:
556
+ """
557
+ Get cost summary for a session.
558
+
559
+ Args:
560
+ session_id: Session to summarize
561
+
562
+ Returns:
563
+ Dictionary with cost breakdown
564
+ """
565
+ conn = self._get_connection()
566
+ cursor = conn.cursor()
567
+
568
+ try:
569
+ cursor.execute(
570
+ """
571
+ SELECT
572
+ tool_name,
573
+ COUNT(*) as call_count,
574
+ SUM(cost_tokens) as total_tokens,
575
+ AVG(cost_tokens) as avg_tokens,
576
+ model
577
+ FROM agent_events
578
+ WHERE session_id = ?
579
+ AND event_type = 'tool_call'
580
+ GROUP BY tool_name, model
581
+ ORDER BY total_tokens DESC
582
+ """,
583
+ (session_id,),
584
+ )
585
+
586
+ breakdown = []
587
+ total_tokens = 0
588
+
589
+ for row in cursor.fetchall():
590
+ tokens = row["total_tokens"] or 0
591
+ total_tokens += tokens
592
+ breakdown.append(
593
+ {
594
+ "tool_name": row["tool_name"],
595
+ "model": row["model"] or "unknown",
596
+ "call_count": row["call_count"],
597
+ "total_tokens": tokens,
598
+ "avg_tokens": row["avg_tokens"] or 0,
599
+ }
600
+ )
601
+
602
+ return {
603
+ "session_id": session_id,
604
+ "total_tokens": total_tokens,
605
+ "breakdown": breakdown,
606
+ "most_expensive_tool": breakdown[0]["tool_name"] if breakdown else None,
607
+ }
608
+
609
+ except sqlite3.Error as e:
610
+ logger.error(f"Error getting cost summary: {e}")
611
+ return {"session_id": session_id, "total_tokens": 0, "breakdown": []}