digitalkin 1.0.0.dev8__tar.gz → 1.0.0.dev9__tar.gz

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 (235) hide show
  1. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/PKG-INFO +1 -1
  2. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/pyproject.toml +1 -1
  3. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/__version__.py +1 -1
  4. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/core/task_manager/redis/redis_client.py +2 -0
  5. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/core/task_manager/redis/redis_signal.py +76 -27
  6. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/core/task_manager/task_executor.py +1 -1
  7. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/grpc_servers/gateway_servicer.py +32 -13
  8. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/grpc_servers/module_server.py +38 -13
  9. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/grpc_servers/module_servicer.py +22 -0
  10. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/settings/redis.py +4 -0
  11. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin.egg-info/PKG-INFO +1 -1
  12. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/LICENSE +0 -0
  13. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/README.md +0 -0
  14. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/base_server/__init__.py +0 -0
  15. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/base_server/mock/__init__.py +0 -0
  16. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/base_server/mock/mock_pb2.py +0 -0
  17. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/base_server/mock/mock_pb2_grpc.py +0 -0
  18. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/base_server/server_async_insecure.py +0 -0
  19. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/base_server/server_async_secure.py +0 -0
  20. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/base_server/server_sync_insecure.py +0 -0
  21. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/base_server/server_sync_secure.py +0 -0
  22. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/bench_module/__init__.py +0 -0
  23. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/bench_module/echo_module.py +0 -0
  24. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/bench_module/models/__init__.py +0 -0
  25. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/bench_module/models/input.py +0 -0
  26. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/bench_module/models/output.py +0 -0
  27. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/bench_module/models/secret.py +0 -0
  28. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/bench_module/models/setup.py +0 -0
  29. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/bench_module/server.py +0 -0
  30. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/bench_module/triggers/__init__.py +0 -0
  31. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/bench_module/triggers/message_trigger.py +0 -0
  32. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/modules/__init__.py +0 -0
  33. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/modules/archetype_with_tools_module.py +0 -0
  34. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/modules/cpu_intensive_module.py +0 -0
  35. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/modules/dynamic_setup_module.py +0 -0
  36. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/modules/minimal_llm_module.py +0 -0
  37. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/modules/text_transform_module.py +0 -0
  38. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/monitoring/digitalkin_observability/__init__.py +0 -0
  39. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/monitoring/digitalkin_observability/http_server.py +0 -0
  40. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/monitoring/digitalkin_observability/interceptors.py +0 -0
  41. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/monitoring/digitalkin_observability/metrics.py +0 -0
  42. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/monitoring/digitalkin_observability/prometheus.py +0 -0
  43. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/monitoring/tests/test_metrics.py +0 -0
  44. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/redis_demo/client.py +0 -0
  45. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/redis_demo/echo_module.py +0 -0
  46. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/redis_demo/models/__init__.py +0 -0
  47. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/redis_demo/models/input.py +0 -0
  48. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/redis_demo/models/output.py +0 -0
  49. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/redis_demo/models/secret.py +0 -0
  50. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/redis_demo/models/setup.py +0 -0
  51. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/redis_demo/server.py +0 -0
  52. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/redis_demo/triggers/__init__.py +0 -0
  53. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/redis_demo/triggers/message_trigger.py +0 -0
  54. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/services/filesystem_module.py +0 -0
  55. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/examples/services/storage_module.py +0 -0
  56. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/setup.cfg +0 -0
  57. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/__init__.py +0 -0
  58. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/community/__init__.py +0 -0
  59. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/community/agno/__init__.py +0 -0
  60. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/community/agno/agno_adapter.py +0 -0
  61. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/community/agno/agui_tools.py +0 -0
  62. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/community/agno/hitl.py +0 -0
  63. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/community/agno/models.py +0 -0
  64. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/core/__init__.py +0 -0
  65. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/core/common/__init__.py +0 -0
  66. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/core/common/factories.py +0 -0
  67. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/core/exceptions.py +0 -0
  68. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/core/job_manager/__init__.py +0 -0
  69. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/core/job_manager/base_job_manager.py +0 -0
  70. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/core/job_manager/single_job_manager.py +0 -0
  71. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/core/profiling/__init__.py +0 -0
  72. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/core/profiling/step_timer.py +0 -0
  73. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/core/profiling/task_profiler.py +0 -0
  74. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/core/resilience/__init__.py +0 -0
  75. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/core/resilience/bulkhead.py +0 -0
  76. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/core/resilience/task_supervisor.py +0 -0
  77. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/core/task_manager/__init__.py +0 -0
  78. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/core/task_manager/base_task_manager.py +0 -0
  79. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/core/task_manager/local_task_manager.py +0 -0
  80. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/core/task_manager/module_runner.py +0 -0
  81. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/core/task_manager/redis/__init__.py +0 -0
  82. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/core/task_manager/redis/instrumented.py +0 -0
  83. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/core/task_manager/redis/proto_streams.py +0 -0
  84. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/core/task_manager/redis/redis_checkpoint.py +0 -0
  85. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/core/task_manager/redis/redis_idempotency.py +0 -0
  86. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/core/task_manager/redis/redis_state.py +0 -0
  87. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/core/task_manager/redis/redis_streams.py +0 -0
  88. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/core/task_manager/redis/shadow.py +0 -0
  89. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/core/task_manager/remote_task_manager.py +0 -0
  90. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/core/task_manager/task_session.py +0 -0
  91. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/exceptions.py +0 -0
  92. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/grpc_servers/__init__.py +0 -0
  93. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/grpc_servers/_base_server.py +0 -0
  94. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/grpc_servers/exceptions.py +0 -0
  95. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/grpc_servers/interceptors/__init__.py +0 -0
  96. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/grpc_servers/interceptors/circuit_breaker_interceptor.py +0 -0
  97. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/grpc_servers/m2m_call_registry.py +0 -0
  98. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/grpc_servers/stream_registry.py +0 -0
  99. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/grpc_servers/stream_session.py +0 -0
  100. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/grpc_servers/utils/__init__.py +0 -0
  101. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/grpc_servers/utils/circuit_breaker.py +0 -0
  102. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/grpc_servers/utils/grpc_client_wrapper.py +0 -0
  103. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/grpc_servers/utils/grpc_error_handler.py +0 -0
  104. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/grpc_servers/utils/utility_schema_extender.py +0 -0
  105. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/grpc_servers/utils/validators.py +0 -0
  106. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/logger.py +0 -0
  107. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/mixins/__init__.py +0 -0
  108. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/mixins/agui_mixin.py +0 -0
  109. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/mixins/base_mixin.py +0 -0
  110. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/mixins/cost_mixin.py +0 -0
  111. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/mixins/file_history_mixin.py +0 -0
  112. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/mixins/filesystem_mixin.py +0 -0
  113. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/mixins/logger_mixin.py +0 -0
  114. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/mixins/storage_mixin.py +0 -0
  115. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/__init__.py +0 -0
  116. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/core/__init__.py +0 -0
  117. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/core/job_manager_models.py +0 -0
  118. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/core/redis.py +0 -0
  119. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/core/task_monitor.py +0 -0
  120. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/events/__init__.py +0 -0
  121. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/events/agent_events.py +0 -0
  122. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/grpc_servers/__init__.py +0 -0
  123. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/grpc_servers/circuit_breaker.py +0 -0
  124. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/grpc_servers/m2m.py +0 -0
  125. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/grpc_servers/models.py +0 -0
  126. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/grpc_servers/stream_error_codes.py +0 -0
  127. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/grpc_servers/types.py +0 -0
  128. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/module/__init__.py +0 -0
  129. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/module/ag_ui.py +0 -0
  130. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/module/base_types.py +0 -0
  131. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/module/module.py +0 -0
  132. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/module/module_context.py +0 -0
  133. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/module/module_types.py +0 -0
  134. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/module/request_metadata.py +0 -0
  135. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/module/select_schema.py +0 -0
  136. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/module/setup_types.py +0 -0
  137. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/module/tool_cache.py +0 -0
  138. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/module/tool_reference.py +0 -0
  139. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/module/utility.py +0 -0
  140. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/services/__init__.py +0 -0
  141. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/services/cost.py +0 -0
  142. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/services/registry.py +0 -0
  143. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/services/services.py +0 -0
  144. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/services/storage.py +0 -0
  145. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/settings/__init__.py +0 -0
  146. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/settings/consumer.py +0 -0
  147. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/settings/gateway.py +0 -0
  148. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/settings/grpc_client.py +0 -0
  149. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/settings/log.py +0 -0
  150. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/settings/module.py +0 -0
  151. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/settings/profiling.py +0 -0
  152. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/settings/queue.py +0 -0
  153. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/settings/resilience.py +0 -0
  154. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/settings/server/__init__.py +0 -0
  155. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/settings/server/channel.py +0 -0
  156. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/settings/server/grpc.py +0 -0
  157. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/settings/server/server.py +0 -0
  158. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/settings/server/servicer.py +0 -0
  159. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/settings/task_manager.py +0 -0
  160. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/settings/utils/__init__.py +0 -0
  161. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/settings/utils/channel.py +0 -0
  162. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/utils/__init__.py +0 -0
  163. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/models/utils/dynamic_schema.py +0 -0
  164. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/modules/__init__.py +0 -0
  165. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/modules/_base_module.py +0 -0
  166. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/modules/archetype_module.py +0 -0
  167. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/modules/tool_module.py +0 -0
  168. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/modules/trigger_handler.py +0 -0
  169. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/modules/triggers/__init__.py +0 -0
  170. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/modules/triggers/healthcheck_ping_trigger.py +0 -0
  171. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/modules/triggers/healthcheck_services_trigger.py +0 -0
  172. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/modules/triggers/healthcheck_status_trigger.py +0 -0
  173. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/py.typed +0 -0
  174. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/__init__.py +0 -0
  175. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/base_strategy.py +0 -0
  176. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/communication/__init__.py +0 -0
  177. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/communication/communication_strategy.py +0 -0
  178. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/communication/default_communication.py +0 -0
  179. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/communication/exceptions.py +0 -0
  180. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/communication/grpc_communication.py +0 -0
  181. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/cost/__init__.py +0 -0
  182. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/cost/cost_strategy.py +0 -0
  183. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/cost/default_cost.py +0 -0
  184. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/cost/exceptions.py +0 -0
  185. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/cost/grpc_cost.py +0 -0
  186. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/filesystem/__init__.py +0 -0
  187. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/filesystem/default_filesystem.py +0 -0
  188. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/filesystem/exceptions.py +0 -0
  189. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/filesystem/filesystem_strategy.py +0 -0
  190. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/filesystem/grpc_filesystem.py +0 -0
  191. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/identity/__init__.py +0 -0
  192. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/identity/default_identity.py +0 -0
  193. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/identity/identity_strategy.py +0 -0
  194. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/registry/__init__.py +0 -0
  195. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/registry/default_registry.py +0 -0
  196. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/registry/exceptions.py +0 -0
  197. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/registry/grpc_registry.py +0 -0
  198. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/registry/registry_models.py +0 -0
  199. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/registry/registry_strategy.py +0 -0
  200. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/services_config.py +0 -0
  201. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/services_models.py +0 -0
  202. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/setup/__init__.py +0 -0
  203. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/setup/default_setup.py +0 -0
  204. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/setup/exceptions.py +0 -0
  205. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/setup/grpc_setup.py +0 -0
  206. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/setup/setup_strategy.py +0 -0
  207. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/storage/__init__.py +0 -0
  208. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/storage/default_storage.py +0 -0
  209. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/storage/exceptions.py +0 -0
  210. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/storage/grpc_storage.py +0 -0
  211. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/storage/storage_strategy.py +0 -0
  212. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/task_manager/__init__.py +0 -0
  213. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/task_manager/default_task_manager.py +0 -0
  214. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/task_manager/exceptions.py +0 -0
  215. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/task_manager/redis_task_manager.py +0 -0
  216. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/task_manager/task_manager_strategy.py +0 -0
  217. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/user_profile/__init__.py +0 -0
  218. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/user_profile/default_user_profile.py +0 -0
  219. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/user_profile/exceptions.py +0 -0
  220. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/user_profile/grpc_user_profile.py +0 -0
  221. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/services/user_profile/user_profile_strategy.py +0 -0
  222. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/utils/__init__.py +0 -0
  223. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/utils/arg_parser.py +0 -0
  224. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/utils/conditional_schema.py +0 -0
  225. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/utils/development_mode_action.py +0 -0
  226. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/utils/dynamic_schema.py +0 -0
  227. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/utils/exceptions.py +0 -0
  228. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/utils/llm_ready_schema.py +0 -0
  229. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/utils/package_discover.py +0 -0
  230. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/utils/proto_utils.py +0 -0
  231. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin/utils/schema_splitter.py +0 -0
  232. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin.egg-info/SOURCES.txt +0 -0
  233. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin.egg-info/dependency_links.txt +0 -0
  234. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin.egg-info/requires.txt +0 -0
  235. {digitalkin-1.0.0.dev8 → digitalkin-1.0.0.dev9}/src/digitalkin.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: digitalkin
3
- Version: 1.0.0.dev8
3
+ Version: 1.0.0.dev9
4
4
  Summary: SDK to build kin used in DigitalKin
5
5
  Author-email: "DigitalKin.ai" <contact@digitalkin.ai>
6
6
  License: Attribution-NonCommercial-ShareAlike 4.0 International
@@ -37,7 +37,7 @@
37
37
  "pydantic-settings>=2.14.1",
38
38
  "redis[hiredis]>=7.4.0,<8",
39
39
  ]
40
- version = "1.0.0.dev8"
40
+ version = "1.0.0.dev9"
41
41
 
42
42
  [project.optional-dependencies]
43
43
  performance = [ "uvloop>=0.21" ]
@@ -5,4 +5,4 @@ from importlib.metadata import PackageNotFoundError, version
5
5
  try:
6
6
  __version__ = version("digitalkin")
7
7
  except PackageNotFoundError:
8
- __version__ = "1.0.0.dev8"
8
+ __version__ = "1.0.0.dev9"
@@ -51,11 +51,13 @@ class RedisClient: # noqa: PLR0904
51
51
  self.url,
52
52
  max_connections=default_size,
53
53
  decode_responses=False,
54
+ health_check_interval=pool.health_check_interval,
54
55
  )
55
56
  self._blocking_client = aioredis.Redis.from_url(
56
57
  self.url,
57
58
  max_connections=blocking_size,
58
59
  decode_responses=False,
60
+ health_check_interval=pool.health_check_interval,
59
61
  )
60
62
 
61
63
  from digitalkin.grpc_servers.utils.validators import GatewayValidator
@@ -7,6 +7,7 @@ import contextlib
7
7
  import json
8
8
  import random
9
9
  import time
10
+ import uuid
10
11
  from typing import TYPE_CHECKING, Any, ClassVar
11
12
 
12
13
  from digitalkin.core.resilience.task_supervisor import log_unhandled
@@ -15,12 +16,20 @@ from digitalkin.logger import logger
15
16
  from digitalkin.models.settings.redis import get_redis_settings
16
17
 
17
18
  if TYPE_CHECKING:
19
+ from collections.abc import Awaitable, Callable
20
+
18
21
  from digitalkin.core.task_manager.task_session import TaskSession
19
22
 
23
+ CacheInvalidator = Callable[[str, str], Awaitable[None]]
24
+
20
25
 
21
26
  class SharedRedisListener:
22
27
  """One PubSub connection per Redis URL; direct-dispatches signals to tasks."""
23
28
 
29
+ PROCESS_ID: ClassVar[str] = uuid.uuid4().hex
30
+ """Per-process UUID generated at class definition; identifies this listener on
31
+ ``signal_ch:_global_`` broadcasts. ``os.getpid()`` collides in Docker (always 1)."""
32
+
24
33
  _instances: ClassVar[dict[str, SharedRedisListener]] = {}
25
34
 
26
35
  @classmethod
@@ -74,6 +83,7 @@ class SharedRedisListener:
74
83
  self._pubsub: Any = None
75
84
  self._listen_task: asyncio.Task[None] | None = None
76
85
  self._stop_event = asyncio.Event()
86
+ self._start_lock = asyncio.Lock()
77
87
  self._counters: dict[str, int] = {
78
88
  "received": 0,
79
89
  "deduped": 0,
@@ -81,58 +91,70 @@ class SharedRedisListener:
81
91
  "dropped": 0,
82
92
  "restarts": 0,
83
93
  "subscribed": 0,
94
+ "invalidated": 0,
84
95
  }
85
96
  self._last_counters_log = time.monotonic()
97
+ self._cache_invalidator: CacheInvalidator | None = None
98
+
99
+ def set_cache_invalidator(self, handler: CacheInvalidator) -> None:
100
+ """Register the ``(action_name, setup_id)`` handler invoked for ``invalidate_*`` signals."""
101
+ self._cache_invalidator = handler
102
+
103
+ async def start(self) -> None:
104
+ """Open PubSub, PSUBSCRIBE ``signal_ch:*``, and start the listen loop. Idempotent under concurrent callers."""
105
+ async with self._start_lock:
106
+ if self._pubsub is not None and self._listen_task is not None and not self._listen_task.done():
107
+ return
108
+ psub_t0 = time.perf_counter_ns()
109
+ self._pubsub = self._redis_client.pubsub()
110
+ await self._pubsub.psubscribe("signal_ch:*")
111
+ psub_ms = (time.perf_counter_ns() - psub_t0) / 1e6
112
+ logger.info(
113
+ "[lat-audit] signal_psubscribe: psubscribe_ms=%.2f pattern=signal_ch:* phase=boot origin=%s",
114
+ psub_ms, SharedRedisListener.PROCESS_ID,
115
+ )
116
+ self._stop_event = asyncio.Event()
117
+ self._listen_task = asyncio.create_task(self._listen_loop(), name="shared_redis_listener")
118
+ self._listen_task.add_done_callback(log_unhandled)
86
119
 
87
- async def register(
120
+ def register(
88
121
  self,
89
122
  task_id: str,
90
123
  session: TaskSession,
91
124
  task: asyncio.Task[None],
92
125
  ) -> None:
93
- """Subscribe ``signal_ch:{task_id}``, store session/task refs, start the listen loop.
126
+ """Store session + task refs; sub-millisecond, never awaits.
94
127
 
95
128
  Raises:
96
- RuntimeError: If max registered tasks is exceeded.
129
+ RuntimeError: If max registered tasks is exceeded or ``start()`` was never called.
97
130
  """
131
+ if self._listen_task is None or self._listen_task.done():
132
+ msg = "SharedRedisListener.register called before start()"
133
+ raise RuntimeError(msg)
98
134
  sig = get_redis_settings().signal
99
135
  if len(self._task_refs) >= sig.max_tasks:
100
136
  msg = f"SharedRedisListener: max tasks ({sig.max_tasks}) exceeded"
101
137
  raise RuntimeError(msg)
102
138
 
103
- sub_t0 = time.perf_counter_ns()
104
- if self._pubsub is None:
105
- self._pubsub = self._redis_client.pubsub()
106
- await self._pubsub.subscribe(f"signal_ch:{task_id}")
107
- sub_ms = (time.perf_counter_ns() - sub_t0) / 1e6
108
-
139
+ reg_t0 = time.perf_counter_ns()
109
140
  self._task_sessions[task_id] = session
110
141
  self._task_refs[task_id] = task
111
142
  task.add_done_callback(lambda _: self.unregister(task_id))
112
143
 
113
- started_loop = False
114
- if self._listen_task is None or self._listen_task.done():
115
- self._stop_event = asyncio.Event()
116
- self._listen_task = asyncio.create_task(self._listen_loop(), name="shared_redis_listener")
117
- self._listen_task.add_done_callback(log_unhandled)
118
- started_loop = True
119
-
120
144
  self._counters["subscribed"] += 1
121
145
  logger.info(
122
- "[lat-audit] signal_subscribe: subscribe_ms=%.2f listen_loop_started=%s active_subs=%d task_id=%s",
123
- sub_ms,
124
- started_loop,
146
+ "[lat-audit] signal_subscribe: register_ms=%.2f active_subs=%d task_id=%s origin=%s",
147
+ (time.perf_counter_ns() - reg_t0) / 1e6,
125
148
  len(self._task_refs),
126
149
  task_id,
150
+ SharedRedisListener.PROCESS_ID,
127
151
  )
128
152
 
129
153
  def unregister(self, task_id: str) -> None:
130
- """Drop the task_id; stop the listen loop when empty."""
154
+ """Drop the task_id. Loop lifetime is process-wide; ``close()`` is the only stop site."""
131
155
  self._task_refs.pop(task_id, None)
132
156
  self._task_sessions.pop(task_id, None)
133
157
  self._last_seen.pop(task_id, None)
134
- if not self._task_refs:
135
- self._stop_event.set()
136
158
 
137
159
  def dispatch_signal(self, task_id: str, data: dict[str, Any], raw_json: str) -> bool:
138
160
  """Route a signal: ``cancel``/``stop`` → side channel + ``task.cancel()``; other actions → audit-only.
@@ -151,6 +173,24 @@ class SharedRedisListener:
151
173
  e2e_ms = (time.time_ns() - pub_ns) / 1e6 if pub_ns else 0.0
152
174
  self._counters["received"] += 1
153
175
 
176
+ if action.startswith("invalidate_"):
177
+ origin = data.get("origin")
178
+ if origin is not None and origin == SharedRedisListener.PROCESS_ID:
179
+ return True
180
+ setup_id = data.get("setup_id", "")
181
+ self._counters["invalidated"] += 1
182
+ logger.info(
183
+ "[lat-audit] signal_invalidate: e2e_ms=%.2f action=%s setup_id=%s",
184
+ e2e_ms, action, setup_id,
185
+ )
186
+ if self._cache_invalidator is not None:
187
+ inv_task = asyncio.create_task(
188
+ self._cache_invalidator(action.upper(), setup_id),
189
+ name=f"invalidate_{action}",
190
+ )
191
+ inv_task.add_done_callback(log_unhandled)
192
+ return True
193
+
154
194
  logger.info(
155
195
  "[lat-audit] signal_dispatch: e2e_ms=%.2f dispatch_ms=%.2f action=%s task_id=%s",
156
196
  e2e_ms,
@@ -184,7 +224,7 @@ class SharedRedisListener:
184
224
  Returns:
185
225
  The triple, or ``None`` if the message is not a usable ``signal_ch:`` payload.
186
226
  """
187
- if msg["type"] != "message":
227
+ if msg["type"] not in {"message", "pmessage"}:
188
228
  return None
189
229
  channel = msg["channel"].decode() if isinstance(msg["channel"], bytes) else msg["channel"]
190
230
  if not channel.startswith("signal_ch:"):
@@ -204,8 +244,14 @@ class SharedRedisListener:
204
244
  while not self._stop_event.is_set():
205
245
  try:
206
246
  if self._pubsub is None:
207
- await asyncio.sleep(0.05)
208
- continue
247
+ self._pubsub = self._redis_client.pubsub()
248
+ psub_t0 = time.perf_counter_ns()
249
+ await self._pubsub.psubscribe("signal_ch:*")
250
+ psub_ms = (time.perf_counter_ns() - psub_t0) / 1e6
251
+ logger.info(
252
+ "[lat-audit] signal_psubscribe: psubscribe_ms=%.2f pattern=signal_ch:* phase=loop",
253
+ psub_ms,
254
+ )
209
255
  msg = await self._pubsub.get_message(ignore_subscribe_messages=True, timeout=0.5)
210
256
  if msg is not None:
211
257
  parsed = self._parse_message(msg)
@@ -225,6 +271,7 @@ class SharedRedisListener:
225
271
  break
226
272
  except Exception:
227
273
  self._counters["restarts"] += 1
274
+ self._pubsub = None
228
275
  logger.exception("SharedRedisListener iteration error, retrying in %.1fs", backoff)
229
276
  await asyncio.sleep(backoff)
230
277
  backoff = min(backoff * 2, 10.0)
@@ -234,7 +281,8 @@ class SharedRedisListener:
234
281
  c = self._counters
235
282
  logger.info(
236
283
  "[lat-audit] signal_counters: received=%d deduped=%d evicted=%d "
237
- "dropped=%d listener_restarts=%d active_subs=%d subscribed_total=%d",
284
+ "dropped=%d listener_restarts=%d active_subs=%d subscribed_total=%d "
285
+ "invalidated=%d",
238
286
  c["received"],
239
287
  c["deduped"],
240
288
  c["evicted"],
@@ -242,6 +290,7 @@ class SharedRedisListener:
242
290
  c["restarts"],
243
291
  len(self._task_refs),
244
292
  c["subscribed"],
293
+ c["invalidated"],
245
294
  )
246
295
  self._last_counters_log = now
247
296
  self._listen_task = None
@@ -258,7 +307,7 @@ class SharedRedisListener:
258
307
  self._last_seen.clear()
259
308
  if self._pubsub is not None:
260
309
  with contextlib.suppress(Exception):
261
- await self._pubsub.unsubscribe()
310
+ await self._pubsub.punsubscribe("signal_ch:*")
262
311
  with contextlib.suppress(Exception):
263
312
  await self._pubsub.aclose()
264
313
  self._pubsub = None
@@ -126,7 +126,7 @@ class TaskExecutor:
126
126
  )
127
127
  else:
128
128
  try:
129
- await listener.register(task_id, session, task)
129
+ listener.register(task_id, session, task)
130
130
  except Exception:
131
131
  logger.warning(
132
132
  "Signal registration failed — signals disabled for task_id=%s",
@@ -17,6 +17,7 @@ from redis.exceptions import RedisError
17
17
 
18
18
  from digitalkin.core.profiling.step_timer import StepTimer
19
19
  from digitalkin.core.task_manager.redis.proto_streams import ProtoStreamReader
20
+ from digitalkin.core.task_manager.redis.redis_signal import SharedRedisListener
20
21
  from digitalkin.grpc_servers.m2m_call_registry import M2MCallRegistry
21
22
  from digitalkin.grpc_servers.stream_registry import StreamRegistry
22
23
  from digitalkin.grpc_servers.stream_session import StreamSession
@@ -130,8 +131,17 @@ class GatewayServicer:
130
131
  return task
131
132
 
132
133
  async def start(self) -> None:
133
- """Start the M2M call-registry TTL sweeper."""
134
+ """Start the M2M call-registry TTL sweeper and PSUBSCRIBE the signal listener."""
134
135
  await self._m2m.start()
136
+ listener = SharedRedisListener.singleton_or_none()
137
+ if listener is not None:
138
+ try:
139
+ await listener.start()
140
+ except Exception:
141
+ logger.warning(
142
+ "SharedRedisListener.start() failed at boot — first-task PSUBSCRIBE will retry lazily",
143
+ exc_info=True,
144
+ )
135
145
 
136
146
  async def stop(self) -> None:
137
147
  """Shut down registries and cancel the M2M sweeper."""
@@ -433,26 +443,35 @@ class GatewayServicer:
433
443
 
434
444
  try:
435
445
  if action_name.startswith("INVALIDATE_"):
446
+ setup_id_for_invalidate = task_id
436
447
  if self._cache_handler is not None:
437
- await self._cache_handler(action_name)
448
+ await self._cache_handler(action_name, setup_id_for_invalidate)
438
449
  timer.mark("cache_handler")
439
450
  last_mark = "cache_handler"
440
- logger.info(
441
- "[lat-audit] SendSignal: %s path=cache total=%.2fms action=%s",
442
- timer.format_steps(),
443
- timer.total_ms(),
444
- action_name,
451
+ payload = json.dumps({
452
+ "action": action_name.lower(),
453
+ "setup_id": setup_id_for_invalidate,
454
+ "published_at_ns": time.time_ns(),
455
+ })
456
+ try:
457
+ await self._redis_client.publish("signal_ch:_global_", payload)
458
+ timer.mark("global_publish")
459
+ last_mark = "global_publish"
460
+ except RedisError:
461
+ logger.warning(
462
+ "[gateway] INVALIDATE fan-out publish failed — local-only invalidation applied",
445
463
  extra=log_extra,
464
+ exc_info=True,
446
465
  )
447
- return gateway_pb2.ClientSignalResponse(success=True, task_id="")
448
- logger.warning(
449
- "[gateway] SendSignal_failed: failure=NoCacheHandler at_step=%s elapsed_ms=%.2f action=%s",
450
- last_mark,
451
- timer.elapsed_now_ms(),
466
+ logger.info(
467
+ "[lat-audit] SendSignal: %s path=cache total=%.2fms action=%s setup_id=%s",
468
+ timer.format_steps(),
469
+ timer.total_ms(),
452
470
  action_name,
471
+ setup_id_for_invalidate,
453
472
  extra=log_extra,
454
473
  )
455
- return gateway_pb2.ClientSignalResponse(success=False, task_id="")
474
+ return gateway_pb2.ClientSignalResponse(success=True, task_id=setup_id_for_invalidate)
456
475
 
457
476
  if GatewayValidator.validate_id(task_id, "task_id") is not None:
458
477
  logger.warning(
@@ -9,6 +9,7 @@ from agentic_mesh_protocol.module.v1 import module_service_pb2, module_service_p
9
9
 
10
10
  from digitalkin.core.task_manager.module_runner import ModuleRunner
11
11
  from digitalkin.core.task_manager.redis import RedisClient
12
+ from digitalkin.core.task_manager.redis.redis_signal import SharedRedisListener
12
13
  from digitalkin.grpc_servers._base_server import BaseServer
13
14
  from digitalkin.grpc_servers.gateway_servicer import GatewayServicer
14
15
  from digitalkin.grpc_servers.module_servicer import ModuleServicer
@@ -129,11 +130,20 @@ class ModuleServer(BaseServer):
129
130
 
130
131
  logger.info("GatewayServicer + ModuleRunner registered (Redis: %s)", redis_url)
131
132
 
132
- async def _handle_cache_invalidation(self, action: str) -> None:
133
- """Dispatch cache invalidation by action name. Isolated from module lifecycle.
133
+ listener = SharedRedisListener.singleton_or_none()
134
+ if listener is not None:
135
+ listener.set_cache_invalidator(self._handle_cache_invalidation)
136
+
137
+ async def _handle_cache_invalidation(self, action: str, setup_id: str = "") -> None:
138
+ """Dispatch cache invalidation by action name.
139
+
140
+ ``INVALIDATE_SETUP`` and ``INVALIDATE_TOOLS`` require a ``setup_id`` —
141
+ without one they log a warning and skip. ``INVALIDATE_ALL`` is the only
142
+ full-wipe path.
134
143
 
135
144
  Args:
136
- action: SignalAction enum name (e.g. "INVALIDATE_SETUP").
145
+ action: SignalAction enum name (e.g. ``INVALIDATE_SETUP``).
146
+ setup_id: Setup identifier for scoped invalidation; ignored by full-wipe actions.
137
147
  """
138
148
  handlers: dict[str, Any] = {
139
149
  "INVALIDATE_ALL": self._invalidate_all,
@@ -144,24 +154,39 @@ class ModuleServer(BaseServer):
144
154
  "INVALIDATE_SHARED": self._invalidate_shared,
145
155
  }
146
156
  handler = handlers.get(action)
147
- if handler is not None:
157
+ if handler is None:
158
+ logger.warning("Unknown invalidation action: %s", action)
159
+ return
160
+ if action in {"INVALIDATE_SETUP", "INVALIDATE_TOOLS"}:
161
+ await handler(setup_id)
162
+ else:
148
163
  await handler()
149
- logger.info("Cache invalidated: %s", action)
164
+ logger.info("Cache invalidated: %s setup_id=%s", action, setup_id or "<all>")
150
165
 
151
166
  async def _invalidate_all(self) -> None:
152
- await self._invalidate_setup()
153
- await self._invalidate_tools()
167
+ if self.module_servicer is not None:
168
+ self.module_servicer.invalidate_setup_cache()
169
+ self.module_servicer.invalidate_tool_cache()
154
170
  await self._invalidate_shared()
155
171
  await self._invalidate_models()
156
172
  await self._invalidate_channels()
157
173
 
158
- async def _invalidate_setup(self) -> None:
159
- if self.module_servicer is not None:
160
- self.module_servicer.invalidate_setup_cache()
174
+ async def _invalidate_setup(self, setup_id: str = "") -> None:
175
+ if self.module_servicer is None:
176
+ return
177
+ if not setup_id:
178
+ logger.warning("INVALIDATE_SETUP received without setup_id — skipping (scoped-only policy)")
179
+ return
180
+ self.module_servicer._setup_cache.pop(setup_id, None) # noqa: SLF001
181
+ self.module_servicer._setup_inflight.pop(setup_id, None) # noqa: SLF001
161
182
 
162
- async def _invalidate_tools(self) -> None:
163
- if self.module_servicer is not None:
164
- self.module_servicer.invalidate_tool_cache()
183
+ async def _invalidate_tools(self, setup_id: str = "") -> None:
184
+ if self.module_servicer is None:
185
+ return
186
+ if not setup_id:
187
+ logger.warning("INVALIDATE_TOOLS received without setup_id — skipping (scoped-only policy)")
188
+ return
189
+ self.module_servicer._tool_cache_by_setup.pop(setup_id, None) # noqa: SLF001
165
190
 
166
191
  async def _invalidate_shared(self) -> None:
167
192
  self.module_class.clear_shared()
@@ -1,6 +1,7 @@
1
1
  """Module servicer implementation for DigitalKin."""
2
2
 
3
3
  import asyncio
4
+ import json
4
5
  import os
5
6
  import time
6
7
  from argparse import ArgumentParser, Namespace
@@ -17,6 +18,7 @@ from google.protobuf import json_format, struct_pb2
17
18
 
18
19
  from digitalkin.core.job_manager.base_job_manager import BaseJobManager
19
20
  from digitalkin.core.job_manager.single_job_manager import SingleJobManager
21
+ from digitalkin.core.task_manager.redis.redis_signal import SharedRedisListener
20
22
  from digitalkin.grpc_servers.exceptions import ServicerError
21
23
  from digitalkin.logger import logger
22
24
  from digitalkin.models.module.module import ModuleCodeModel
@@ -387,6 +389,26 @@ class ModuleServicer(module_service_pb2_grpc.ModuleServiceServicer, ArgParser):
387
389
  ),
388
390
  )
389
391
  self._tool_cache_by_setup.pop(setup_version.setup_id, None)
392
+
393
+ publish_ns = time.time_ns()
394
+ for action in ("invalidate_setup", "invalidate_tools"):
395
+ payload = json.dumps({
396
+ "action": action,
397
+ "setup_id": setup_version.setup_id,
398
+ "published_at_ns": publish_ns,
399
+ "origin": SharedRedisListener.PROCESS_ID,
400
+ })
401
+ try:
402
+ await self._redis_client.publish("signal_ch:_global_", payload)
403
+ except Exception:
404
+ logger.warning(
405
+ "[gateway] cache-invalidate fan-out publish failed for action=%s "
406
+ "setup_id=%s — peers may keep stale cache until TTL",
407
+ action,
408
+ setup_version.setup_id,
409
+ exc_info=True,
410
+ )
411
+
390
412
  setup_version.content = json_format.ParseDict( # type: ignore[misc]
391
413
  updated_setup_data,
392
414
  struct_pb2.Struct(),
@@ -16,6 +16,10 @@ class RedisPoolSettings(BaseSettings):
16
16
  pool_size_default: int = Field(default=0, description="Non-blocking pool size (0 = pool_size // 2)")
17
17
  pool_size_blocking: int = Field(default=0, description="Blocking pool size for XREAD (0 = pool_size // 2)")
18
18
  health_check_timeout: float = Field(default=5.0, description="Max seconds to wait for a PING during health check.")
19
+ health_check_interval: int = Field(
20
+ default=15,
21
+ description="Seconds between connection-level PINGs; 0 disables. Catches silently-dead sockets.",
22
+ )
19
23
 
20
24
  def get_default_pool_size(self) -> int:
21
25
  """Non-blocking pool size, defaults to half of total.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: digitalkin
3
- Version: 1.0.0.dev8
3
+ Version: 1.0.0.dev9
4
4
  Summary: SDK to build kin used in DigitalKin
5
5
  Author-email: "DigitalKin.ai" <contact@digitalkin.ai>
6
6
  License: Attribution-NonCommercial-ShareAlike 4.0 International
File without changes