solace-agent-mesh 1.5.1__py3-none-any.whl → 1.6.0__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.

Potentially problematic release.


This version of solace-agent-mesh might be problematic. Click here for more details.

Files changed (180) hide show
  1. solace_agent_mesh/agent/adk/callbacks.py +0 -5
  2. solace_agent_mesh/agent/adk/models/lite_llm.py +123 -8
  3. solace_agent_mesh/agent/adk/models/oauth2_token_manager.py +245 -0
  4. solace_agent_mesh/agent/protocol/event_handlers.py +40 -1
  5. solace_agent_mesh/agent/proxies/__init__.py +0 -0
  6. solace_agent_mesh/agent/proxies/a2a/__init__.py +3 -0
  7. solace_agent_mesh/agent/proxies/a2a/app.py +55 -0
  8. solace_agent_mesh/agent/proxies/a2a/component.py +1115 -0
  9. solace_agent_mesh/agent/proxies/a2a/config.py +140 -0
  10. solace_agent_mesh/agent/proxies/a2a/oauth_token_cache.py +104 -0
  11. solace_agent_mesh/agent/proxies/base/__init__.py +3 -0
  12. solace_agent_mesh/agent/proxies/base/app.py +99 -0
  13. solace_agent_mesh/agent/proxies/base/component.py +619 -0
  14. solace_agent_mesh/agent/proxies/base/config.py +85 -0
  15. solace_agent_mesh/agent/proxies/base/proxy_task_context.py +17 -0
  16. solace_agent_mesh/agent/sac/app.py +9 -3
  17. solace_agent_mesh/agent/sac/component.py +160 -8
  18. solace_agent_mesh/agent/tools/audio_tools.py +125 -8
  19. solace_agent_mesh/agent/tools/web_tools.py +10 -5
  20. solace_agent_mesh/agent/utils/artifact_helpers.py +141 -3
  21. solace_agent_mesh/assets/docs/404.html +3 -3
  22. solace_agent_mesh/assets/docs/assets/js/5c2bd65f.eda4bcb2.js +1 -0
  23. solace_agent_mesh/assets/docs/assets/js/6ad8f0bd.f4b15f3b.js +1 -0
  24. solace_agent_mesh/assets/docs/assets/js/71da7b71.38583438.js +1 -0
  25. solace_agent_mesh/assets/docs/assets/js/77cf947d.48cb18a2.js +1 -0
  26. solace_agent_mesh/assets/docs/assets/js/924ffdeb.8095e148.js +1 -0
  27. solace_agent_mesh/assets/docs/assets/js/9e9d0a82.570c057b.js +1 -0
  28. solace_agent_mesh/assets/docs/assets/js/{ad71b5ed.60668e9e.js → ad71b5ed.af3ecfd1.js} +1 -1
  29. solace_agent_mesh/assets/docs/assets/js/ceb2a7a6.5d92d7d0.js +1 -0
  30. solace_agent_mesh/assets/docs/assets/js/{da0b5bad.9d369087.js → da0b5bad.d08a9466.js} +1 -1
  31. solace_agent_mesh/assets/docs/assets/js/db924877.e98d12a1.js +1 -0
  32. solace_agent_mesh/assets/docs/assets/js/de915948.27d6b065.js +1 -0
  33. solace_agent_mesh/assets/docs/assets/js/e6f9706b.e74a984d.js +1 -0
  34. solace_agent_mesh/assets/docs/assets/js/f284c35a.42f59cdd.js +1 -0
  35. solace_agent_mesh/assets/docs/assets/js/ff4d71f2.15b02f97.js +1 -0
  36. solace_agent_mesh/assets/docs/assets/js/{main.bd3c34f3.js → main.20feee82.js} +2 -2
  37. solace_agent_mesh/assets/docs/assets/js/runtime~main.0d198646.js +1 -0
  38. solace_agent_mesh/assets/docs/docs/documentation/components/agents/index.html +15 -4
  39. solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/artifact-management/index.html +4 -4
  40. solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/audio-tools/index.html +4 -4
  41. solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/data-analysis-tools/index.html +4 -4
  42. solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/embeds/index.html +4 -4
  43. solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/index.html +4 -4
  44. solace_agent_mesh/assets/docs/docs/documentation/components/cli/index.html +4 -4
  45. solace_agent_mesh/assets/docs/docs/documentation/components/gateways/index.html +4 -4
  46. solace_agent_mesh/assets/docs/docs/documentation/components/index.html +4 -4
  47. solace_agent_mesh/assets/docs/docs/documentation/components/orchestrator/index.html +4 -4
  48. solace_agent_mesh/assets/docs/docs/documentation/components/plugins/index.html +4 -4
  49. solace_agent_mesh/assets/docs/docs/documentation/components/proxies/index.html +262 -0
  50. solace_agent_mesh/assets/docs/docs/documentation/deploying/debugging/index.html +3 -3
  51. solace_agent_mesh/assets/docs/docs/documentation/deploying/deployment-options/index.html +31 -3
  52. solace_agent_mesh/assets/docs/docs/documentation/deploying/index.html +3 -3
  53. solace_agent_mesh/assets/docs/docs/documentation/deploying/observability/index.html +3 -3
  54. solace_agent_mesh/assets/docs/docs/documentation/developing/create-agents/index.html +4 -4
  55. solace_agent_mesh/assets/docs/docs/documentation/developing/create-gateways/index.html +5 -5
  56. solace_agent_mesh/assets/docs/docs/documentation/developing/creating-python-tools/index.html +4 -4
  57. solace_agent_mesh/assets/docs/docs/documentation/developing/creating-service-providers/index.html +4 -4
  58. solace_agent_mesh/assets/docs/docs/documentation/developing/evaluations/index.html +135 -0
  59. solace_agent_mesh/assets/docs/docs/documentation/developing/index.html +6 -4
  60. solace_agent_mesh/assets/docs/docs/documentation/developing/structure/index.html +4 -4
  61. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/bedrock-agents/index.html +4 -4
  62. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/custom-agent/index.html +4 -4
  63. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/event-mesh-gateway/index.html +5 -5
  64. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/mcp-integration/index.html +4 -4
  65. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/mongodb-integration/index.html +4 -4
  66. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/rag-integration/index.html +4 -4
  67. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/rest-gateway/index.html +4 -4
  68. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/slack-integration/index.html +4 -4
  69. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/sql-database/index.html +4 -4
  70. solace_agent_mesh/assets/docs/docs/documentation/enterprise/index.html +3 -3
  71. solace_agent_mesh/assets/docs/docs/documentation/enterprise/installation/index.html +3 -3
  72. solace_agent_mesh/assets/docs/docs/documentation/enterprise/rbac-setup-guide/index.html +3 -3
  73. solace_agent_mesh/assets/docs/docs/documentation/enterprise/single-sign-on/index.html +3 -3
  74. solace_agent_mesh/assets/docs/docs/documentation/getting-started/architecture/index.html +3 -3
  75. solace_agent_mesh/assets/docs/docs/documentation/getting-started/index.html +3 -3
  76. solace_agent_mesh/assets/docs/docs/documentation/getting-started/introduction/index.html +3 -3
  77. solace_agent_mesh/assets/docs/docs/documentation/getting-started/try-agent-mesh/index.html +3 -3
  78. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/configurations/index.html +6 -5
  79. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/index.html +3 -3
  80. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/installation/index.html +3 -3
  81. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/large_language_models/index.html +100 -3
  82. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/run-project/index.html +3 -3
  83. solace_agent_mesh/assets/docs/docs/documentation/migrations/a2a-upgrade/a2a-gateway-upgrade-to-0.3.0/index.html +3 -3
  84. solace_agent_mesh/assets/docs/docs/documentation/migrations/a2a-upgrade/a2a-technical-migration-map/index.html +3 -3
  85. solace_agent_mesh/assets/docs/lunr-index-1761165361160.json +1 -0
  86. solace_agent_mesh/assets/docs/lunr-index.json +1 -1
  87. solace_agent_mesh/assets/docs/search-doc-1761165361160.json +1 -0
  88. solace_agent_mesh/assets/docs/search-doc.json +1 -1
  89. solace_agent_mesh/assets/docs/sitemap.xml +1 -1
  90. solace_agent_mesh/cli/__init__.py +1 -1
  91. solace_agent_mesh/cli/commands/add_cmd/agent_cmd.py +2 -69
  92. solace_agent_mesh/cli/commands/eval_cmd.py +11 -49
  93. solace_agent_mesh/cli/commands/init_cmd/__init__.py +0 -5
  94. solace_agent_mesh/cli/commands/init_cmd/env_step.py +10 -12
  95. solace_agent_mesh/cli/commands/init_cmd/orchestrator_step.py +9 -61
  96. solace_agent_mesh/cli/commands/init_cmd/webui_gateway_step.py +9 -49
  97. solace_agent_mesh/cli/commands/plugin_cmd/add_cmd.py +1 -2
  98. solace_agent_mesh/client/webui/frontend/static/assets/{authCallback-DwrxZE0E.js → authCallback-BTf6dqwp.js} +1 -1
  99. solace_agent_mesh/client/webui/frontend/static/assets/{client-DarGQzyw.js → client-CaY59VuC.js} +1 -1
  100. solace_agent_mesh/client/webui/frontend/static/assets/main-BGTaW0uv.js +342 -0
  101. solace_agent_mesh/client/webui/frontend/static/assets/main-DHJKSW1S.css +1 -0
  102. solace_agent_mesh/client/webui/frontend/static/assets/{vendor-BKIeiHj_.js → vendor-BEmvJSYz.js} +1 -1
  103. solace_agent_mesh/client/webui/frontend/static/auth-callback.html +3 -3
  104. solace_agent_mesh/client/webui/frontend/static/index.html +4 -4
  105. solace_agent_mesh/common/a2a/__init__.py +24 -0
  106. solace_agent_mesh/common/a2a/artifact.py +39 -0
  107. solace_agent_mesh/common/a2a/events.py +29 -0
  108. solace_agent_mesh/common/a2a/message.py +68 -0
  109. solace_agent_mesh/common/a2a/protocol.py +73 -1
  110. solace_agent_mesh/common/agent_registry.py +83 -3
  111. solace_agent_mesh/common/constants.py +3 -1
  112. solace_agent_mesh/common/utils/pydantic_utils.py +12 -0
  113. solace_agent_mesh/config_portal/backend/common.py +1 -1
  114. solace_agent_mesh/config_portal/frontend/static/client/assets/_index-ByU1X1HD.js +98 -0
  115. solace_agent_mesh/config_portal/frontend/static/client/assets/{manifest-44d62be6.js → manifest-61038fc6.js} +1 -1
  116. solace_agent_mesh/config_portal/frontend/static/client/index.html +1 -1
  117. solace_agent_mesh/evaluation/evaluator.py +128 -104
  118. solace_agent_mesh/evaluation/message_organizer.py +116 -110
  119. solace_agent_mesh/evaluation/report_data_processor.py +84 -86
  120. solace_agent_mesh/evaluation/report_generator.py +73 -79
  121. solace_agent_mesh/evaluation/run.py +421 -235
  122. solace_agent_mesh/evaluation/shared/__init__.py +92 -0
  123. solace_agent_mesh/evaluation/shared/constants.py +47 -0
  124. solace_agent_mesh/evaluation/shared/exceptions.py +50 -0
  125. solace_agent_mesh/evaluation/shared/helpers.py +35 -0
  126. solace_agent_mesh/evaluation/shared/test_case_loader.py +167 -0
  127. solace_agent_mesh/evaluation/shared/test_suite_loader.py +280 -0
  128. solace_agent_mesh/evaluation/subscriber.py +111 -232
  129. solace_agent_mesh/evaluation/summary_builder.py +227 -117
  130. solace_agent_mesh/gateway/base/app.py +1 -1
  131. solace_agent_mesh/gateway/base/component.py +8 -1
  132. solace_agent_mesh/gateway/http_sse/alembic/versions/20251015_add_session_performance_indexes.py +70 -0
  133. solace_agent_mesh/gateway/http_sse/component.py +98 -2
  134. solace_agent_mesh/gateway/http_sse/dependencies.py +4 -4
  135. solace_agent_mesh/gateway/http_sse/main.py +2 -1
  136. solace_agent_mesh/gateway/http_sse/repository/chat_task_repository.py +12 -13
  137. solace_agent_mesh/gateway/http_sse/repository/feedback_repository.py +15 -18
  138. solace_agent_mesh/gateway/http_sse/repository/interfaces.py +25 -18
  139. solace_agent_mesh/gateway/http_sse/repository/session_repository.py +30 -26
  140. solace_agent_mesh/gateway/http_sse/repository/task_repository.py +35 -44
  141. solace_agent_mesh/gateway/http_sse/routers/agent_cards.py +4 -3
  142. solace_agent_mesh/gateway/http_sse/routers/artifacts.py +95 -203
  143. solace_agent_mesh/gateway/http_sse/routers/dto/responses/session_responses.py +4 -3
  144. solace_agent_mesh/gateway/http_sse/routers/sessions.py +2 -2
  145. solace_agent_mesh/gateway/http_sse/routers/tasks.py +33 -41
  146. solace_agent_mesh/gateway/http_sse/routers/visualization.py +17 -11
  147. solace_agent_mesh/gateway/http_sse/services/data_retention_service.py +4 -4
  148. solace_agent_mesh/gateway/http_sse/services/feedback_service.py +51 -43
  149. solace_agent_mesh/gateway/http_sse/services/session_service.py +20 -20
  150. solace_agent_mesh/gateway/http_sse/services/task_logger_service.py +8 -8
  151. solace_agent_mesh/gateway/http_sse/shared/base_repository.py +45 -71
  152. solace_agent_mesh/gateway/http_sse/shared/types.py +0 -18
  153. solace_agent_mesh/templates/gateway_config_template.yaml +0 -5
  154. solace_agent_mesh/templates/logging_config_template.ini +10 -6
  155. solace_agent_mesh/templates/plugin_gateway_config_template.yaml +0 -3
  156. solace_agent_mesh/templates/shared_config.yaml +40 -0
  157. {solace_agent_mesh-1.5.1.dist-info → solace_agent_mesh-1.6.0.dist-info}/METADATA +47 -21
  158. {solace_agent_mesh-1.5.1.dist-info → solace_agent_mesh-1.6.0.dist-info}/RECORD +162 -141
  159. solace_agent_mesh/assets/docs/assets/js/5c2bd65f.e49689dd.js +0 -1
  160. solace_agent_mesh/assets/docs/assets/js/6ad8f0bd.39d5851d.js +0 -1
  161. solace_agent_mesh/assets/docs/assets/js/71da7b71.804d6567.js +0 -1
  162. solace_agent_mesh/assets/docs/assets/js/77cf947d.64c9bd6c.js +0 -1
  163. solace_agent_mesh/assets/docs/assets/js/9e9d0a82.dd810042.js +0 -1
  164. solace_agent_mesh/assets/docs/assets/js/db924877.cbc66f02.js +0 -1
  165. solace_agent_mesh/assets/docs/assets/js/de915948.139b4b9c.js +0 -1
  166. solace_agent_mesh/assets/docs/assets/js/e6f9706b.582a78ca.js +0 -1
  167. solace_agent_mesh/assets/docs/assets/js/f284c35a.5766a13d.js +0 -1
  168. solace_agent_mesh/assets/docs/assets/js/ff4d71f2.9c0297a6.js +0 -1
  169. solace_agent_mesh/assets/docs/assets/js/runtime~main.18dc45dd.js +0 -1
  170. solace_agent_mesh/assets/docs/lunr-index-1760121512891.json +0 -1
  171. solace_agent_mesh/assets/docs/search-doc-1760121512891.json +0 -1
  172. solace_agent_mesh/client/webui/frontend/static/assets/main-2nd1gbaH.js +0 -339
  173. solace_agent_mesh/client/webui/frontend/static/assets/main-DoKXctCM.css +0 -1
  174. solace_agent_mesh/config_portal/frontend/static/client/assets/_index-BNuqpWDc.js +0 -98
  175. solace_agent_mesh/evaluation/config_loader.py +0 -657
  176. solace_agent_mesh/evaluation/test_case_loader.py +0 -714
  177. /solace_agent_mesh/assets/docs/assets/js/{main.bd3c34f3.js.LICENSE.txt → main.20feee82.js.LICENSE.txt} +0 -0
  178. {solace_agent_mesh-1.5.1.dist-info → solace_agent_mesh-1.6.0.dist-info}/WHEEL +0 -0
  179. {solace_agent_mesh-1.5.1.dist-info → solace_agent_mesh-1.6.0.dist-info}/entry_points.txt +0 -0
  180. {solace_agent_mesh-1.5.1.dist-info → solace_agent_mesh-1.6.0.dist-info}/licenses/LICENSE +0 -0
@@ -272,7 +272,7 @@ class BaseGatewayApp(App):
272
272
  broker_config["queue_name"] = (
273
273
  f"{self.namespace.strip('/')}/q/gdk/gateway/{self.gateway_id}"
274
274
  )
275
- broker_config["temporary_queue"] = True
275
+ broker_config["temporary_queue"] = modified_app_info.get("broker", {}).get("temporary_queue", True)
276
276
  log.debug(
277
277
  "Injected broker settings for gateway '%s': %s",
278
278
  self.gateway_id,
@@ -282,6 +282,9 @@ class BaseGatewayComponent(SamComponentBase):
282
282
  "user_id_for_a2a", user_identity.get("id")
283
283
  )
284
284
 
285
+ system_purpose = self.get_config("system_purpose", "")
286
+ response_format = self.get_config("response_format", "")
287
+
285
288
  if not a2a_session_id:
286
289
  a2a_session_id = f"gdk-session-{uuid.uuid4().hex}"
287
290
  log.warning(
@@ -291,7 +294,11 @@ class BaseGatewayComponent(SamComponentBase):
291
294
  )
292
295
  external_request_context["a2a_session_id"] = a2a_session_id
293
296
 
294
- a2a_metadata = {"agent_name": target_agent_name}
297
+ a2a_metadata = {
298
+ "agent_name": target_agent_name,
299
+ "system_purpose": system_purpose,
300
+ "response_format": response_format,
301
+ }
295
302
  invoked_artifacts = external_request_context.get("invoked_with_artifacts")
296
303
  if invoked_artifacts:
297
304
  a2a_metadata["invoked_with_artifacts"] = invoked_artifacts
@@ -0,0 +1,70 @@
1
+ """add performance indexes for query optimization
2
+
3
+ Revision ID: 20251015_session_idx
4
+ Revises: 98882922fa59
5
+ Create Date: 2025-10-15
6
+
7
+ """
8
+
9
+ from collections.abc import Sequence
10
+
11
+ import sqlalchemy as sa
12
+ from alembic import op
13
+
14
+ revision: str = "20251015_session_idx"
15
+ down_revision: str | Sequence[str] | None = "98882922fa59"
16
+ branch_labels: str | Sequence[str] | None = None
17
+ depends_on: str | Sequence[str] | None = None
18
+
19
+
20
+ def upgrade() -> None:
21
+ """Add composite indexes for optimal query performance."""
22
+
23
+ op.create_index("ix_sessions_user_id", "sessions", ["user_id"], unique=False)
24
+
25
+ op.create_index(
26
+ "ix_sessions_user_updated",
27
+ "sessions",
28
+ ["user_id", sa.text("updated_time DESC")],
29
+ unique=False,
30
+ )
31
+
32
+ op.create_index(
33
+ "ix_chat_tasks_session_user_created",
34
+ "chat_tasks",
35
+ ["session_id", "user_id", "created_time"],
36
+ unique=False,
37
+ )
38
+
39
+ op.create_index(
40
+ "ix_tasks_user_start_time",
41
+ "tasks",
42
+ ["user_id", sa.text("start_time DESC")],
43
+ unique=False,
44
+ )
45
+
46
+ op.create_index(
47
+ "ix_task_events_task_created",
48
+ "task_events",
49
+ ["task_id", "created_time"],
50
+ unique=False,
51
+ )
52
+
53
+ op.drop_index("ix_tasks_initial_request_text", table_name="tasks")
54
+
55
+
56
+ def downgrade() -> None:
57
+ """Remove performance indexes."""
58
+
59
+ op.create_index(
60
+ op.f("ix_tasks_initial_request_text"),
61
+ "tasks",
62
+ ["initial_request_text"],
63
+ unique=False,
64
+ )
65
+
66
+ op.drop_index("ix_task_events_task_created", table_name="task_events")
67
+ op.drop_index("ix_tasks_user_start_time", table_name="tasks")
68
+ op.drop_index("ix_chat_tasks_session_user_created", table_name="chat_tasks")
69
+ op.drop_index("ix_sessions_user_updated", table_name="sessions")
70
+ op.drop_index("ix_sessions_user_id", table_name="sessions")
@@ -146,6 +146,27 @@ class WebUIBackendComponent(BaseGatewayComponent):
146
146
  timer_id=self._sse_cleanup_timer_id,
147
147
  interval_ms=cleanup_interval_sec * 1000,
148
148
  )
149
+
150
+ # Set up health check timer for agent registry
151
+ from ...common.constants import HEALTH_CHECK_INTERVAL_SECONDS
152
+ self.health_check_timer_id = f"agent_health_check_{self.gateway_id}"
153
+ health_check_interval_seconds = self.get_config("agent_health_check_interval_seconds", HEALTH_CHECK_INTERVAL_SECONDS)
154
+ if health_check_interval_seconds > 0:
155
+ log.info(
156
+ "%s Scheduling agent health check every %d seconds.",
157
+ self.log_identifier,
158
+ health_check_interval_seconds,
159
+ )
160
+ self.add_timer(
161
+ delay_ms=health_check_interval_seconds * 1000,
162
+ timer_id=self.health_check_timer_id,
163
+ interval_ms=health_check_interval_seconds * 1000,
164
+ )
165
+ else:
166
+ log.warning(
167
+ "%s Agent health check interval not configured or invalid, health checks will not run periodically.",
168
+ self.log_identifier,
169
+ )
149
170
 
150
171
  session_config = self._resolve_session_config()
151
172
  if session_config.get("type") == "sql":
@@ -265,6 +286,10 @@ class WebUIBackendComponent(BaseGatewayComponent):
265
286
  log.debug("%s SSE buffer cleanup timer triggered.", self.log_identifier)
266
287
  self.sse_event_buffer.cleanup_stale_buffers()
267
288
  return
289
+ elif event.data.get("timer_id") == self.health_check_timer_id:
290
+ log.debug("%s Agent health check timer triggered.", self.log_identifier)
291
+ self._check_agent_health()
292
+ return
268
293
 
269
294
  if timer_id == self._data_retention_timer_id:
270
295
  log.debug("%s Data retention cleanup timer triggered.", self.log_identifier)
@@ -354,7 +379,7 @@ class WebUIBackendComponent(BaseGatewayComponent):
354
379
  ),
355
380
  "retry_interval": main_broker_config.get("retry_interval"),
356
381
  "retry_count": main_broker_config.get("retry_count"),
357
- "temporary_queue": True,
382
+ "temporary_queue": main_broker_config.get("temporary_queue", True),
358
383
  },
359
384
  }
360
385
 
@@ -486,7 +511,7 @@ class WebUIBackendComponent(BaseGatewayComponent):
486
511
  ),
487
512
  "retry_interval": main_broker_config.get("retry_interval"),
488
513
  "retry_count": main_broker_config.get("retry_count"),
489
- "temporary_queue": True,
514
+ "temporary_queue": main_broker_config.get("temporary_queue", True),
490
515
  },
491
516
  }
492
517
 
@@ -1404,6 +1429,7 @@ class WebUIBackendComponent(BaseGatewayComponent):
1404
1429
  self.data_retention_service = None
1405
1430
  log.info("%s Data retention service cleaned up.", self.log_identifier)
1406
1431
 
1432
+ self.cancel_timer(self.health_check_timer_id)
1407
1433
  log.info("%s Cleaning up visualization resources...", self.log_identifier)
1408
1434
  if self._visualization_message_queue:
1409
1435
  self._visualization_message_queue.put(None)
@@ -1729,6 +1755,76 @@ class WebUIBackendComponent(BaseGatewayComponent):
1729
1755
 
1730
1756
  def get_agent_registry(self) -> AgentRegistry:
1731
1757
  return self.agent_registry
1758
+
1759
+ def _check_agent_health(self):
1760
+ """
1761
+ Checks the health of peer agents and de-registers unresponsive ones.
1762
+ This is called periodically by the health check timer.
1763
+ Uses TTL-based expiration to determine if an agent is unresponsive.
1764
+ """
1765
+
1766
+ log.debug("%s Performing agent health check...", self.log_identifier)
1767
+
1768
+ # Get TTL from configuration or use default from constants
1769
+ from ...common.constants import HEALTH_CHECK_TTL_SECONDS, HEALTH_CHECK_INTERVAL_SECONDS
1770
+ ttl_seconds = self.get_config("agent_health_check_ttl_seconds", HEALTH_CHECK_TTL_SECONDS)
1771
+ health_check_interval = self.get_config("agent_health_check_interval_seconds", HEALTH_CHECK_INTERVAL_SECONDS)
1772
+
1773
+ log.debug(
1774
+ "%s Health check configuration: interval=%d seconds, TTL=%d seconds",
1775
+ self.log_identifier,
1776
+ health_check_interval,
1777
+ ttl_seconds
1778
+ )
1779
+
1780
+ # Validate configuration values
1781
+ if ttl_seconds <= 0 or health_check_interval <= 0 or ttl_seconds < health_check_interval:
1782
+ log.error(
1783
+ "%s agent_health_check_ttl_seconds (%d) and agent_health_check_interval_seconds (%d) must be positive and TTL must be greater than interval.",
1784
+ self.log_identifier,
1785
+ ttl_seconds,
1786
+ health_check_interval
1787
+ )
1788
+ raise ValueError(f"Invalid health check configuration. agent_health_check_ttl_seconds ({ttl_seconds}) and agent_health_check_interval_seconds ({health_check_interval}) must be positive and TTL must be greater than interval.")
1789
+
1790
+ # Get all agent names from the registry
1791
+ agent_names = self.agent_registry.get_agent_names()
1792
+ total_agents = len(agent_names)
1793
+ agents_to_deregister = []
1794
+
1795
+ log.debug("%s Checking health of %d peer agents", self.log_identifier, total_agents)
1796
+
1797
+ for agent_name in agent_names:
1798
+ # Check if the agent's TTL has expired
1799
+ is_expired, time_since_last_seen = self.agent_registry.check_ttl_expired(agent_name, ttl_seconds)
1800
+
1801
+ if is_expired:
1802
+ log.warning(
1803
+ "%s Agent '%s' TTL has expired. De-registering. Time since last seen: %d seconds (TTL: %d seconds)",
1804
+ self.log_identifier,
1805
+ agent_name,
1806
+ time_since_last_seen,
1807
+ ttl_seconds
1808
+ )
1809
+ agents_to_deregister.append(agent_name)
1810
+
1811
+ # De-register unresponsive agents
1812
+ for agent_name in agents_to_deregister:
1813
+ self._deregister_agent(agent_name)
1814
+
1815
+ log.info(
1816
+ "%s Agent health check completed. Total agents: %d, De-registered: %d",
1817
+ self.log_identifier,
1818
+ total_agents,
1819
+ len(agents_to_deregister)
1820
+ )
1821
+
1822
+ def _deregister_agent(self, agent_name: str):
1823
+ """
1824
+ De-registers an agent from the registry and publishes a de-registration event.
1825
+ """
1826
+ # Remove from registry
1827
+ self.agent_registry.remove_agent(agent_name)
1732
1828
 
1733
1829
  def get_sse_manager(self) -> SSEManager:
1734
1830
  return self.sse_manager
@@ -243,10 +243,10 @@ def get_people_service(
243
243
  return PeopleService(identity_service=identity_service)
244
244
 
245
245
 
246
- def get_task_repository(db: Session = Depends(get_db)) -> ITaskRepository:
246
+ def get_task_repository() -> ITaskRepository:
247
247
  """FastAPI dependency to get an instance of TaskRepository."""
248
248
  log.debug("[Dependencies] get_task_repository called")
249
- return TaskRepository(db)
249
+ return TaskRepository()
250
250
 
251
251
 
252
252
  def get_feedback_service(
@@ -525,9 +525,9 @@ def get_session_validator(
525
525
  try:
526
526
  db = SessionLocal()
527
527
  try:
528
- session_repository = SessionRepository(db)
528
+ session_repository = SessionRepository()
529
529
  session_domain = session_repository.find_user_session(
530
- session_id, user_id
530
+ db, session_id, user_id
531
531
  )
532
532
  return session_domain is not None
533
533
  finally:
@@ -97,7 +97,8 @@ async def _get_user_info(
97
97
 
98
98
  def _extract_user_identifier(user_info: dict) -> str:
99
99
  user_identifier = (
100
- user_info.get("sub")
100
+ user_info.get("user_id") # internal /user_info endpoint format maps identifier to user_id
101
+ or user_info.get("sub")
101
102
  or user_info.get("client_id")
102
103
  or user_info.get("username")
103
104
  or user_info.get("oid")
@@ -16,12 +16,9 @@ from .models import ChatTaskModel
16
16
  class ChatTaskRepository(IChatTaskRepository):
17
17
  """SQLAlchemy implementation of chat task repository."""
18
18
 
19
- def __init__(self, db: DBSession):
20
- self.db = db
21
-
22
- def save(self, task: ChatTask) -> ChatTask:
19
+ def save(self, session: DBSession, task: ChatTask) -> ChatTask:
23
20
  """Save or update a chat task (upsert)."""
24
- existing = self.db.query(ChatTaskModel).filter(
21
+ existing = session.query(ChatTaskModel).filter(
25
22
  ChatTaskModel.id == task.id
26
23
  ).first()
27
24
 
@@ -43,12 +40,12 @@ class ChatTaskRepository(IChatTaskRepository):
43
40
  created_time=task.created_time,
44
41
  updated_time=task.updated_time
45
42
  )
46
- self.db.add(model)
43
+ session.add(model)
47
44
 
48
- self.db.commit()
45
+ session.flush()
49
46
 
50
47
  # Reload to get updated values
51
- model = self.db.query(ChatTaskModel).filter(
48
+ model = session.query(ChatTaskModel).filter(
52
49
  ChatTaskModel.id == task.id
53
50
  ).first()
54
51
 
@@ -56,11 +53,12 @@ class ChatTaskRepository(IChatTaskRepository):
56
53
 
57
54
  def find_by_session(
58
55
  self,
56
+ session: DBSession,
59
57
  session_id: SessionId,
60
58
  user_id: UserId
61
59
  ) -> List[ChatTask]:
62
60
  """Find all tasks for a session."""
63
- models = self.db.query(ChatTaskModel).filter(
61
+ models = session.query(ChatTaskModel).filter(
64
62
  ChatTaskModel.session_id == session_id,
65
63
  ChatTaskModel.user_id == user_id
66
64
  ).order_by(ChatTaskModel.created_time.asc()).all()
@@ -69,23 +67,24 @@ class ChatTaskRepository(IChatTaskRepository):
69
67
 
70
68
  def find_by_id(
71
69
  self,
70
+ session: DBSession,
72
71
  task_id: str,
73
72
  user_id: UserId
74
73
  ) -> Optional[ChatTask]:
75
74
  """Find a specific task."""
76
- model = self.db.query(ChatTaskModel).filter(
75
+ model = session.query(ChatTaskModel).filter(
77
76
  ChatTaskModel.id == task_id,
78
77
  ChatTaskModel.user_id == user_id
79
78
  ).first()
80
79
 
81
80
  return self._model_to_entity(model) if model else None
82
81
 
83
- def delete_by_session(self, session_id: SessionId) -> bool:
82
+ def delete_by_session(self, session: DBSession, session_id: SessionId) -> bool:
84
83
  """Delete all tasks for a session."""
85
- result = self.db.query(ChatTaskModel).filter(
84
+ result = session.query(ChatTaskModel).filter(
86
85
  ChatTaskModel.session_id == session_id
87
86
  ).delete()
88
- self.db.commit()
87
+ session.flush()
89
88
  return result > 0
90
89
 
91
90
  def _model_to_entity(self, model: ChatTaskModel) -> ChatTask:
@@ -12,10 +12,7 @@ from .models import FeedbackModel
12
12
  class FeedbackRepository(IFeedbackRepository):
13
13
  """SQLAlchemy implementation of feedback repository."""
14
14
 
15
- def __init__(self, db: DBSession):
16
- self.db = db
17
-
18
- def save(self, feedback: Feedback) -> Feedback:
15
+ def save(self, session: DBSession, feedback: Feedback) -> Feedback:
19
16
  """Save feedback."""
20
17
  model = FeedbackModel(
21
18
  id=feedback.id,
@@ -26,12 +23,12 @@ class FeedbackRepository(IFeedbackRepository):
26
23
  comment=feedback.comment,
27
24
  created_time=feedback.created_time,
28
25
  )
29
- self.db.add(model)
30
- self.db.commit()
31
- self.db.refresh(model)
26
+ session.add(model)
27
+ session.flush()
28
+ session.refresh(model)
32
29
  return self._model_to_entity(model)
33
30
 
34
- def delete_feedback_older_than(self, cutoff_time_ms: int, batch_size: int) -> int:
31
+ def delete_feedback_older_than(self, session: DBSession, cutoff_time_ms: int, batch_size: int) -> int:
35
32
  """
36
33
  Delete feedback records older than the cutoff time.
37
34
  Uses batch deletion to avoid long-running transactions.
@@ -44,36 +41,36 @@ class FeedbackRepository(IFeedbackRepository):
44
41
  Total number of feedback records deleted
45
42
  """
46
43
  total_deleted = 0
47
-
44
+
48
45
  while True:
49
46
  # Find a batch of feedback IDs to delete
50
47
  feedback_ids_to_delete = (
51
- self.db.query(FeedbackModel.id)
48
+ session.query(FeedbackModel.id)
52
49
  .filter(FeedbackModel.created_time < cutoff_time_ms)
53
50
  .limit(batch_size)
54
51
  .all()
55
52
  )
56
-
53
+
57
54
  if not feedback_ids_to_delete:
58
55
  break
59
-
56
+
60
57
  # Extract IDs from the result tuples
61
58
  ids = [feedback_id[0] for feedback_id in feedback_ids_to_delete]
62
-
59
+
63
60
  # Delete this batch
64
61
  deleted_count = (
65
- self.db.query(FeedbackModel)
62
+ session.query(FeedbackModel)
66
63
  .filter(FeedbackModel.id.in_(ids))
67
64
  .delete(synchronize_session=False)
68
65
  )
69
-
70
- self.db.commit()
66
+
67
+ session.commit()
71
68
  total_deleted += deleted_count
72
-
69
+
73
70
  # If we deleted fewer than batch_size, we're done
74
71
  if deleted_count < batch_size:
75
72
  break
76
-
73
+
77
74
  return total_deleted
78
75
 
79
76
  def _model_to_entity(self, model: FeedbackModel) -> Feedback:
@@ -4,8 +4,10 @@ Repository interfaces defining contracts for data access.
4
4
 
5
5
  from abc import ABC, abstractmethod
6
6
  from typing import TYPE_CHECKING, Optional
7
+ from sqlalchemy.orm import Session as DBSession
7
8
 
8
- from ..shared.types import PaginationInfo, PaginationParams, SessionId, UserId
9
+ from ..shared.pagination import PaginationParams
10
+ from ..shared.types import SessionId, UserId
9
11
  from .entities import Feedback, Session, Task, TaskEvent
10
12
 
11
13
  if TYPE_CHECKING:
@@ -14,33 +16,33 @@ if TYPE_CHECKING:
14
16
 
15
17
  class ISessionRepository(ABC):
16
18
  """Interface for session data access operations."""
17
-
19
+
18
20
  @abstractmethod
19
21
  def find_by_user(
20
- self, user_id: UserId, pagination: PaginationParams | None = None
22
+ self, session: DBSession, user_id: UserId, pagination: PaginationParams | None = None
21
23
  ) -> list[Session]:
22
24
  """Find all sessions for a specific user."""
23
25
  pass
24
26
 
25
27
  @abstractmethod
26
- def count_by_user(self, user_id: UserId) -> int:
28
+ def count_by_user(self, session: DBSession, user_id: UserId) -> int:
27
29
  """Count total sessions for a specific user."""
28
30
  pass
29
31
 
30
32
  @abstractmethod
31
33
  def find_user_session(
32
- self, session_id: SessionId, user_id: UserId
34
+ self, session: DBSession, session_id: SessionId, user_id: UserId
33
35
  ) -> Session | None:
34
36
  """Find a specific session belonging to a user."""
35
37
  pass
36
38
 
37
39
  @abstractmethod
38
- def save(self, session: Session) -> Session:
40
+ def save(self, session: DBSession, session_obj: Session) -> Session:
39
41
  """Save or update a session."""
40
42
  pass
41
43
 
42
44
  @abstractmethod
43
- def delete(self, session_id: SessionId, user_id: UserId) -> bool:
45
+ def delete(self, session: DBSession, session_id: SessionId, user_id: UserId) -> bool:
44
46
  """Delete a session belonging to a user."""
45
47
  pass
46
48
 
@@ -49,28 +51,31 @@ class ITaskRepository(ABC):
49
51
  """Interface for task data access operations."""
50
52
 
51
53
  @abstractmethod
52
- def save_task(self, task: Task) -> Task:
54
+ def save_task(self, session: DBSession, task: Task) -> Task:
53
55
  """Create or update a task."""
54
56
  pass
55
57
 
56
58
  @abstractmethod
57
- def save_event(self, event: TaskEvent) -> TaskEvent:
59
+ def save_event(self, session: DBSession, event: TaskEvent) -> TaskEvent:
58
60
  """Save a task event."""
59
61
  pass
60
62
 
61
63
  @abstractmethod
62
- def find_by_id(self, task_id: str) -> Task | None:
64
+ def find_by_id(self, session: DBSession, task_id: str) -> Task | None:
63
65
  """Find a task by its ID."""
64
66
  pass
65
67
 
66
68
  @abstractmethod
67
- def find_by_id_with_events(self, task_id: str) -> tuple[Task, list[TaskEvent]] | None:
69
+ def find_by_id_with_events(
70
+ self, session: DBSession, task_id: str
71
+ ) -> tuple[Task, list[TaskEvent]] | None:
68
72
  """Find a task with all its events."""
69
73
  pass
70
74
 
71
75
  @abstractmethod
72
76
  def search(
73
77
  self,
78
+ session: DBSession,
74
79
  user_id: UserId,
75
80
  start_date: int | None = None,
76
81
  end_date: int | None = None,
@@ -81,7 +86,7 @@ class ITaskRepository(ABC):
81
86
  pass
82
87
 
83
88
  @abstractmethod
84
- def delete_tasks_older_than(self, cutoff_time_ms: int, batch_size: int) -> int:
89
+ def delete_tasks_older_than(self, session: DBSession, cutoff_time_ms: int, batch_size: int) -> int:
85
90
  """Delete tasks older than cutoff time using batch deletion."""
86
91
  pass
87
92
 
@@ -90,12 +95,12 @@ class IFeedbackRepository(ABC):
90
95
  """Interface for feedback data access operations."""
91
96
 
92
97
  @abstractmethod
93
- def save(self, feedback: Feedback) -> Feedback:
98
+ def save(self, session: DBSession, feedback: Feedback) -> Feedback:
94
99
  """Save feedback."""
95
100
  pass
96
101
 
97
102
  @abstractmethod
98
- def delete_feedback_older_than(self, cutoff_time_ms: int, batch_size: int) -> int:
103
+ def delete_feedback_older_than(self, session: DBSession, cutoff_time_ms: int, batch_size: int) -> int:
99
104
  """Delete feedback older than cutoff time using batch deletion."""
100
105
  pass
101
106
 
@@ -104,21 +109,23 @@ class IChatTaskRepository(ABC):
104
109
  """Interface for chat task data access operations."""
105
110
 
106
111
  @abstractmethod
107
- def save(self, task: "ChatTask") -> "ChatTask":
112
+ def save(self, session: DBSession, task: "ChatTask") -> "ChatTask":
108
113
  """Save or update a chat task (upsert)."""
109
114
  pass
110
115
 
111
116
  @abstractmethod
112
- def find_by_session(self, session_id: SessionId, user_id: UserId) -> list["ChatTask"]:
117
+ def find_by_session(
118
+ self, session: DBSession, session_id: SessionId, user_id: UserId
119
+ ) -> list["ChatTask"]:
113
120
  """Find all tasks for a session."""
114
121
  pass
115
122
 
116
123
  @abstractmethod
117
- def find_by_id(self, task_id: str, user_id: UserId) -> Optional["ChatTask"]:
124
+ def find_by_id(self, session: DBSession, task_id: str, user_id: UserId) -> Optional["ChatTask"]:
118
125
  """Find a specific task."""
119
126
  pass
120
127
 
121
128
  @abstractmethod
122
- def delete_by_session(self, session_id: SessionId) -> bool:
129
+ def delete_by_session(self, session: DBSession, session_id: SessionId) -> bool:
123
130
  """Delete all tasks for a session."""
124
131
  pass