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
htmlgraph/learning.py CHANGED
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  """
2
4
  Active Learning Persistence Module.
3
5
 
@@ -5,7 +7,6 @@ Bridges TranscriptAnalytics to the HtmlGraph for persistent learning.
5
7
  Analyzes sessions and persists patterns, insights, and metrics to the graph.
6
8
  """
7
9
 
8
- from __future__ import annotations
9
10
 
10
11
  from collections import Counter
11
12
  from datetime import datetime
@@ -150,68 +151,86 @@ class LearningPersistence:
150
151
  return health
151
152
 
152
153
  def persist_patterns(self, min_count: int = 2) -> list[str]:
153
- """Detect and persist workflow patterns from sessions.
154
+ """Detect and persist workflow patterns IN SESSIONS (not as separate files).
155
+
156
+ This refactored version stores patterns inline within session HTML files
157
+ to avoid creating 2,890+ individual pattern files.
154
158
 
155
159
  Args:
156
160
  min_count: Minimum occurrences to persist a pattern
157
161
 
158
162
  Returns:
159
- List of persisted pattern IDs
163
+ List of session IDs that had patterns updated
160
164
  """
161
- # Collect tool sequences from all sessions
162
- # Use session_manager to get full Session objects with activity_log
163
- sequences: list[tuple[Any, ...]] = []
165
+ # Collect tool sequences per session (not globally)
166
+ session_ids_updated: list[str] = []
167
+
164
168
  for session in self.sdk.session_manager.session_converter.load_all():
165
- if session.activity_log:
166
- tools = [
167
- a.tool if not isinstance(a, dict) else a.get("tool", "")
168
- for a in session.activity_log
169
- ]
170
- # Extract 3-tool sequences
171
- for i in range(len(tools) - 2):
172
- seq = tools[i : i + 3]
173
- if all(seq): # No empty tools
174
- sequences.append(tuple(seq))
175
-
176
- # Count sequences
177
- seq_counts = Counter(sequences)
178
-
179
- # Persist patterns with min_count
180
- pattern_ids: list[str | Any] = []
181
- for seq, count in seq_counts.items(): # type: ignore[assignment]
182
- if count >= min_count:
183
- # Check if pattern already exists
184
- existing = self.sdk.patterns.find_by_sequence(list(seq))
185
- if existing:
186
- # Update count - use properties dict for updates
187
- pattern = existing[0]
188
- pattern.properties["detection_count"] = count
189
- pattern.properties["last_detected"] = datetime.now().isoformat()
190
- self.sdk.patterns.update(pattern)
191
- pattern_ids.append(pattern.id)
192
- else:
193
- # Create new pattern using builder methods
194
- pattern_type = self._classify_pattern(list(seq))
195
- now = datetime.now()
196
- pattern = (
197
- self.sdk.patterns.create(f"Pattern: {' -> '.join(seq)}")
198
- .set_sequence(list(seq))
199
- .set_pattern_type(pattern_type)
200
- .set_detection_count(count)
201
- .set_first_detected(now)
202
- .set_last_detected(now)
203
- .save()
169
+ if not session.activity_log:
170
+ continue
171
+
172
+ # Extract 3-tool sequences from this session
173
+ tools = [
174
+ a.tool if not isinstance(a, dict) else a.get("tool", "")
175
+ for a in session.activity_log
176
+ ]
177
+
178
+ # Count sequences in this session
179
+ sequences: list[tuple[Any, ...]] = []
180
+ for i in range(len(tools) - 2):
181
+ seq = tools[i : i + 3]
182
+ if all(seq): # No empty tools
183
+ sequences.append(tuple(seq))
184
+
185
+ seq_counts = Counter(sequences)
186
+
187
+ # Update session's detected_patterns
188
+ patterns_updated = False
189
+ for seq, count in seq_counts.items(): # type: ignore[assignment]
190
+ if count >= min_count:
191
+ # Check if pattern already exists in this session
192
+ existing = next(
193
+ (
194
+ p
195
+ for p in session.detected_patterns
196
+ if p.get("sequence") == list(seq)
197
+ ),
198
+ None,
204
199
  )
205
- pattern_ids.append(pattern.id)
200
+
201
+ if existing:
202
+ # Update existing pattern
203
+ existing["detection_count"] = count
204
+ existing["last_detected"] = datetime.now().isoformat()
205
+ patterns_updated = True
206
+ else:
207
+ # Add new pattern to session
208
+ pattern_type = self._classify_pattern(list(seq))
209
+ now = datetime.now()
210
+ session.detected_patterns.append(
211
+ {
212
+ "sequence": list(seq),
213
+ "pattern_type": pattern_type,
214
+ "detection_count": count,
215
+ "first_detected": now.isoformat(),
216
+ "last_detected": now.isoformat(),
217
+ }
218
+ )
219
+ patterns_updated = True
220
+
221
+ # Save updated session if patterns were modified
222
+ if patterns_updated:
223
+ self.sdk.session_manager.session_converter.save(session)
224
+ session_ids_updated.append(session.id)
206
225
 
207
226
  # Also persist parallel patterns
208
- parallel_pattern_ids = self.persist_parallel_patterns(min_count=min_count)
209
- pattern_ids.extend(parallel_pattern_ids)
227
+ parallel_session_ids = self.persist_parallel_patterns(min_count=min_count)
228
+ session_ids_updated.extend(parallel_session_ids)
210
229
 
211
- return pattern_ids
230
+ return session_ids_updated
212
231
 
213
232
  def persist_parallel_patterns(self, min_count: int = 2) -> list[str]:
214
- """Detect and persist parallel execution patterns from sessions.
233
+ """Detect and persist parallel execution patterns IN SESSIONS.
215
234
 
216
235
  Identifies when multiple tools are invoked in parallel (same parent_activity_id).
217
236
  This is especially useful for detecting orchestrator patterns like parallel Task delegation.
@@ -220,12 +239,11 @@ class LearningPersistence:
220
239
  min_count: Minimum occurrences to persist a pattern
221
240
 
222
241
  Returns:
223
- List of persisted pattern IDs
242
+ List of session IDs that had parallel patterns updated
224
243
  """
225
244
  from collections import defaultdict
226
245
 
227
- # Collect parallel execution groups from all sessions
228
- parallel_patterns: list[tuple[str, ...]] = []
246
+ session_ids_updated: list[str] = []
229
247
 
230
248
  for session in self.sdk.session_manager.session_converter.load_all():
231
249
  if not session.activity_log:
@@ -242,12 +260,12 @@ class LearningPersistence:
242
260
  if parent_id: # Only track activities with a parent
243
261
  parent_groups[parent_id].append(activity)
244
262
 
245
- # Detect parallel patterns (2+ activities with same parent)
263
+ # Collect parallel patterns for this session
264
+ parallel_patterns: list[tuple[str, ...]] = []
246
265
  for parent_id, activities in parent_groups.items():
247
266
  if len(activities) < 2:
248
267
  continue
249
268
 
250
- # Check if activities overlap in time (parallel execution)
251
269
  # Sort by timestamp
252
270
  sorted_activities = sorted(
253
271
  activities,
@@ -268,50 +286,57 @@ class LearningPersistence:
268
286
  if all(tools):
269
287
  parallel_patterns.append(tools)
270
288
 
271
- # Count parallel patterns
272
- pattern_counts = Counter(parallel_patterns)
273
-
274
- # Persist patterns with min_count
275
- pattern_ids: list[str | Any] = []
276
- for tools, count in pattern_counts.items():
277
- if count >= min_count:
278
- # Create a pattern name that indicates parallelism
279
- tool_names = list(tools)
280
- pattern_name = f"Parallel[{len(tools)}]: {' || '.join(tools)}"
281
-
282
- # Check if pattern already exists
283
- existing = self.sdk.patterns.find_by_sequence(tool_names)
284
- if existing:
285
- # Update existing pattern
286
- pattern = existing[0]
287
- pattern.properties = pattern.properties or {}
288
- pattern.properties["detection_count"] = count
289
- pattern.properties["last_detected"] = datetime.now().isoformat()
290
- pattern.properties["parallel_count"] = len(tools)
291
- pattern.properties["is_parallel"] = True
292
- self.sdk.patterns.update(pattern)
293
- pattern_ids.append(pattern.id)
294
- else:
295
- # Create new parallel pattern
296
- pattern_type = self._classify_pattern(tool_names, is_parallel=True)
297
- now = datetime.now()
298
- pattern = (
299
- self.sdk.patterns.create(pattern_name)
300
- .set_sequence(tool_names)
301
- .set_pattern_type(pattern_type)
302
- .set_detection_count(count)
303
- .set_first_detected(now)
304
- .set_last_detected(now)
305
- .save()
289
+ # Count parallel patterns in this session
290
+ pattern_counts = Counter(parallel_patterns)
291
+
292
+ # Update session's detected_patterns with parallel patterns
293
+ patterns_updated = False
294
+ for tools, count in pattern_counts.items():
295
+ if count >= min_count:
296
+ tool_names = list(tools)
297
+
298
+ # Check if pattern already exists in this session
299
+ # Parallel patterns have special naming: "Parallel[N]: tool1 || tool2"
300
+ existing = next(
301
+ (
302
+ p
303
+ for p in session.detected_patterns
304
+ if p.get("sequence") == tool_names
305
+ and p.get("is_parallel", False)
306
+ ),
307
+ None,
306
308
  )
307
- # Mark as parallel in properties
308
- pattern.properties = pattern.properties or {}
309
- pattern.properties["parallel_count"] = len(tools)
310
- pattern.properties["is_parallel"] = True
311
- self.sdk.patterns.update(pattern)
312
- pattern_ids.append(pattern.id)
313
309
 
314
- return pattern_ids
310
+ if existing:
311
+ # Update existing parallel pattern
312
+ existing["detection_count"] = count
313
+ existing["last_detected"] = datetime.now().isoformat()
314
+ patterns_updated = True
315
+ else:
316
+ # Add new parallel pattern to session
317
+ pattern_type = self._classify_pattern(
318
+ tool_names, is_parallel=True
319
+ )
320
+ now = datetime.now()
321
+ session.detected_patterns.append(
322
+ {
323
+ "sequence": tool_names,
324
+ "pattern_type": pattern_type,
325
+ "detection_count": count,
326
+ "first_detected": now.isoformat(),
327
+ "last_detected": now.isoformat(),
328
+ "is_parallel": True,
329
+ "parallel_count": len(tools),
330
+ }
331
+ )
332
+ patterns_updated = True
333
+
334
+ # Save updated session if patterns were modified
335
+ if patterns_updated:
336
+ self.sdk.session_manager.session_converter.save(session)
337
+ session_ids_updated.append(session.id)
338
+
339
+ return session_ids_updated
315
340
 
316
341
  def _classify_pattern(self, sequence: list[str], is_parallel: bool = False) -> str:
317
342
  """Classify a pattern as optimal, anti-pattern, or neutral.
@@ -329,17 +354,17 @@ class LearningPersistence:
329
354
  if is_parallel:
330
355
  # Parallel Task delegation is optimal (orchestrator pattern)
331
356
  if all(tool == "Task" for tool in sequence) and len(sequence) >= 2:
332
- return "orchestrator-optimal"
357
+ return "optimal"
333
358
  # Mixed parallel operations can also be optimal
334
359
  if "Task" in sequence:
335
- return "orchestrator-optimal"
360
+ return "optimal"
336
361
  # Other parallel patterns are neutral
337
362
  return "neutral"
338
363
 
339
364
  # Sequential anti-patterns for orchestrators
340
365
  # Multiple sequential Tasks without parallelism is an anti-pattern
341
366
  if seq == ("Task", "Task", "Task"):
342
- return "orchestrator-anti-pattern"
367
+ return "anti-pattern"
343
368
 
344
369
  # Known optimal patterns (sequential)
345
370
  optimal = [
htmlgraph/mcp_server.py CHANGED
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  """
2
4
  Minimal MCP (Model Context Protocol) server for HtmlGraph.
3
5
 
@@ -24,7 +26,6 @@ Example SDK usage:
24
26
  f.status = "done"
25
27
  """
26
28
 
27
- from __future__ import annotations
28
29
 
29
30
  import json
30
31
  import os