digitalkin 1.0.0.dev0__tar.gz → 1.0.0.dev2__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 (215) hide show
  1. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/PKG-INFO +1 -1
  2. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/pyproject.toml +1 -1
  3. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/__version__.py +1 -1
  4. digitalkin-1.0.0.dev2/src/digitalkin/core/task_manager/module_runner.py +170 -0
  5. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/grpc_servers/gateway_constants.py +2 -1
  6. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/grpc_servers/gateway_servicer.py +40 -58
  7. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/grpc_servers/module_server.py +11 -36
  8. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/grpc_servers/module_servicer.py +41 -7
  9. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/models/settings/gateway.py +11 -4
  10. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/communication/gateway_consumer.py +7 -3
  11. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/registry/grpc_registry.py +10 -12
  12. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin.egg-info/PKG-INFO +1 -1
  13. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin.egg-info/SOURCES.txt +1 -1
  14. digitalkin-1.0.0.dev0/src/digitalkin/core/task_manager/task_dispatcher.py +0 -316
  15. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/LICENSE +0 -0
  16. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/README.md +0 -0
  17. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/base_server/__init__.py +0 -0
  18. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/base_server/mock/__init__.py +0 -0
  19. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/base_server/mock/mock_pb2.py +0 -0
  20. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/base_server/mock/mock_pb2_grpc.py +0 -0
  21. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/base_server/server_async_insecure.py +0 -0
  22. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/base_server/server_async_secure.py +0 -0
  23. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/base_server/server_sync_insecure.py +0 -0
  24. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/base_server/server_sync_secure.py +0 -0
  25. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/bench_module/__init__.py +0 -0
  26. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/bench_module/echo_module.py +0 -0
  27. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/bench_module/models/__init__.py +0 -0
  28. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/bench_module/models/input.py +0 -0
  29. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/bench_module/models/output.py +0 -0
  30. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/bench_module/models/secret.py +0 -0
  31. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/bench_module/models/setup.py +0 -0
  32. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/bench_module/server.py +0 -0
  33. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/bench_module/triggers/__init__.py +0 -0
  34. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/bench_module/triggers/message_trigger.py +0 -0
  35. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/modules/__init__.py +0 -0
  36. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/modules/archetype_with_tools_module.py +0 -0
  37. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/modules/cpu_intensive_module.py +0 -0
  38. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/modules/dynamic_setup_module.py +0 -0
  39. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/modules/minimal_llm_module.py +0 -0
  40. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/modules/text_transform_module.py +0 -0
  41. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/monitoring/digitalkin_observability/__init__.py +0 -0
  42. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/monitoring/digitalkin_observability/http_server.py +0 -0
  43. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/monitoring/digitalkin_observability/interceptors.py +0 -0
  44. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/monitoring/digitalkin_observability/metrics.py +0 -0
  45. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/monitoring/digitalkin_observability/prometheus.py +0 -0
  46. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/monitoring/tests/test_metrics.py +0 -0
  47. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/redis_demo/client.py +0 -0
  48. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/redis_demo/echo_module.py +0 -0
  49. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/redis_demo/models/__init__.py +0 -0
  50. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/redis_demo/models/input.py +0 -0
  51. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/redis_demo/models/output.py +0 -0
  52. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/redis_demo/models/secret.py +0 -0
  53. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/redis_demo/models/setup.py +0 -0
  54. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/redis_demo/server.py +0 -0
  55. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/redis_demo/triggers/__init__.py +0 -0
  56. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/redis_demo/triggers/message_trigger.py +0 -0
  57. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/services/filesystem_module.py +0 -0
  58. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/services/storage_module.py +0 -0
  59. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/setup.cfg +0 -0
  60. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/__init__.py +0 -0
  61. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/community/__init__.py +0 -0
  62. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/community/agno/__init__.py +0 -0
  63. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/community/agno/agno_adapter.py +0 -0
  64. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/community/agno/agui_tools.py +0 -0
  65. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/community/agno/hitl.py +0 -0
  66. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/core/__init__.py +0 -0
  67. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/core/common/__init__.py +0 -0
  68. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/core/common/factories.py +0 -0
  69. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/core/job_manager/__init__.py +0 -0
  70. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/core/job_manager/base_job_manager.py +0 -0
  71. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/core/job_manager/single_job_manager.py +0 -0
  72. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/core/profiling/__init__.py +0 -0
  73. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/core/profiling/step_timer.py +0 -0
  74. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/core/profiling/task_profiler.py +0 -0
  75. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/core/resilience/__init__.py +0 -0
  76. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/core/resilience/bulkhead.py +0 -0
  77. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/core/resilience/graceful_shutdown.py +0 -0
  78. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/core/resilience/session_reaper.py +0 -0
  79. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/core/resilience/watchdog.py +0 -0
  80. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/core/task_manager/__init__.py +0 -0
  81. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/core/task_manager/base_task_manager.py +0 -0
  82. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/core/task_manager/local_task_manager.py +0 -0
  83. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/core/task_manager/redis/__init__.py +0 -0
  84. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/core/task_manager/redis/instrumented.py +0 -0
  85. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/core/task_manager/redis/proto_streams.py +0 -0
  86. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/core/task_manager/redis/redis_checkpoint.py +0 -0
  87. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/core/task_manager/redis/redis_client.py +0 -0
  88. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/core/task_manager/redis/redis_idempotency.py +0 -0
  89. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/core/task_manager/redis/redis_signal.py +0 -0
  90. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/core/task_manager/redis/redis_state.py +0 -0
  91. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/core/task_manager/redis/redis_streams.py +0 -0
  92. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/core/task_manager/redis/shadow.py +0 -0
  93. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/core/task_manager/remote_task_manager.py +0 -0
  94. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/core/task_manager/task_executor.py +0 -0
  95. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/core/task_manager/task_session.py +0 -0
  96. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/core/task_manager/task_wrapper.py +0 -0
  97. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/grpc_servers/__init__.py +0 -0
  98. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/grpc_servers/_base_server.py +0 -0
  99. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/grpc_servers/interceptors/__init__.py +0 -0
  100. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/grpc_servers/interceptors/circuit_breaker_interceptor.py +0 -0
  101. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/grpc_servers/stream_error_codes.py +0 -0
  102. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/grpc_servers/stream_registry.py +0 -0
  103. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/grpc_servers/stream_session.py +0 -0
  104. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/grpc_servers/utils/__init__.py +0 -0
  105. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/grpc_servers/utils/circuit_breaker.py +0 -0
  106. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/grpc_servers/utils/exceptions.py +0 -0
  107. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/grpc_servers/utils/grpc_client_wrapper.py +0 -0
  108. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/grpc_servers/utils/grpc_error_handler.py +0 -0
  109. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/grpc_servers/utils/utility_schema_extender.py +0 -0
  110. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/logger.py +0 -0
  111. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/mixins/__init__.py +0 -0
  112. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/mixins/agui_mixin.py +0 -0
  113. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/mixins/base_mixin.py +0 -0
  114. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/mixins/cost_mixin.py +0 -0
  115. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/mixins/file_history_mixin.py +0 -0
  116. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/mixins/filesystem_mixin.py +0 -0
  117. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/mixins/logger_mixin.py +0 -0
  118. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/mixins/storage_mixin.py +0 -0
  119. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/models/__init__.py +0 -0
  120. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/models/core/__init__.py +0 -0
  121. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/models/core/job_manager_models.py +0 -0
  122. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/models/core/task_monitor.py +0 -0
  123. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/models/events/__init__.py +0 -0
  124. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/models/events/agent_events.py +0 -0
  125. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/models/grpc_servers/__init__.py +0 -0
  126. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/models/grpc_servers/models.py +0 -0
  127. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/models/grpc_servers/types.py +0 -0
  128. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/models/module/__init__.py +0 -0
  129. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/models/module/ag_ui.py +0 -0
  130. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/models/module/base_types.py +0 -0
  131. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/models/module/module.py +0 -0
  132. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/models/module/module_context.py +0 -0
  133. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/models/module/module_types.py +0 -0
  134. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/models/module/request_metadata.py +0 -0
  135. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/models/module/select_schema.py +0 -0
  136. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/models/module/setup_types.py +0 -0
  137. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/models/module/tool_cache.py +0 -0
  138. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/models/module/tool_reference.py +0 -0
  139. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/models/module/utility.py +0 -0
  140. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/models/services/__init__.py +0 -0
  141. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/models/services/cost.py +0 -0
  142. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/models/services/registry.py +0 -0
  143. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/models/services/storage.py +0 -0
  144. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/models/settings/__init__.py +0 -0
  145. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/models/settings/consumer.py +0 -0
  146. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/models/settings/profiling.py +0 -0
  147. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/models/settings/redis.py +0 -0
  148. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/models/settings/server/__init__.py +0 -0
  149. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/models/settings/server/channel.py +0 -0
  150. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/models/settings/server/grpc.py +0 -0
  151. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/models/settings/server/server.py +0 -0
  152. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/models/settings/utils/__init__.py +0 -0
  153. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/models/settings/utils/channel.py +0 -0
  154. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/modules/__init__.py +0 -0
  155. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/modules/_base_module.py +0 -0
  156. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/modules/archetype_module.py +0 -0
  157. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/modules/tool_module.py +0 -0
  158. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/modules/trigger_handler.py +0 -0
  159. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/modules/triggers/__init__.py +0 -0
  160. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/modules/triggers/healthcheck_ping_trigger.py +0 -0
  161. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/modules/triggers/healthcheck_services_trigger.py +0 -0
  162. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/modules/triggers/healthcheck_status_trigger.py +0 -0
  163. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/py.typed +0 -0
  164. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/__init__.py +0 -0
  165. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/base_strategy.py +0 -0
  166. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/communication/__init__.py +0 -0
  167. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/communication/communication_strategy.py +0 -0
  168. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/communication/default_communication.py +0 -0
  169. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/communication/grpc_communication.py +0 -0
  170. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/cost/__init__.py +0 -0
  171. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/cost/cost_strategy.py +0 -0
  172. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/cost/default_cost.py +0 -0
  173. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/cost/grpc_cost.py +0 -0
  174. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/filesystem/__init__.py +0 -0
  175. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/filesystem/default_filesystem.py +0 -0
  176. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/filesystem/filesystem_strategy.py +0 -0
  177. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/filesystem/grpc_filesystem.py +0 -0
  178. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/identity/__init__.py +0 -0
  179. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/identity/default_identity.py +0 -0
  180. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/identity/identity_strategy.py +0 -0
  181. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/registry/__init__.py +0 -0
  182. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/registry/default_registry.py +0 -0
  183. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/registry/exceptions.py +0 -0
  184. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/registry/registry_models.py +0 -0
  185. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/registry/registry_strategy.py +0 -0
  186. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/services_config.py +0 -0
  187. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/services_models.py +0 -0
  188. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/setup/__init__.py +0 -0
  189. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/setup/default_setup.py +0 -0
  190. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/setup/grpc_setup.py +0 -0
  191. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/setup/setup_strategy.py +0 -0
  192. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/storage/__init__.py +0 -0
  193. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/storage/default_storage.py +0 -0
  194. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/storage/grpc_storage.py +0 -0
  195. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/storage/storage_strategy.py +0 -0
  196. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/task_manager/__init__.py +0 -0
  197. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/task_manager/default_task_manager.py +0 -0
  198. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/task_manager/redis_task_manager.py +0 -0
  199. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/task_manager/task_manager_strategy.py +0 -0
  200. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/user_profile/__init__.py +0 -0
  201. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/user_profile/default_user_profile.py +0 -0
  202. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/user_profile/grpc_user_profile.py +0 -0
  203. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/user_profile/user_profile_strategy.py +0 -0
  204. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/utils/__init__.py +0 -0
  205. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/utils/arg_parser.py +0 -0
  206. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/utils/conditional_schema.py +0 -0
  207. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/utils/development_mode_action.py +0 -0
  208. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/utils/dynamic_schema.py +0 -0
  209. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/utils/llm_ready_schema.py +0 -0
  210. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/utils/package_discover.py +0 -0
  211. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/utils/proto_utils.py +0 -0
  212. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/utils/schema_splitter.py +0 -0
  213. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin.egg-info/dependency_links.txt +0 -0
  214. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin.egg-info/requires.txt +0 -0
  215. {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/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.dev0
3
+ Version: 1.0.0.dev2
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.0",
38
38
  "redis[hiredis]>=7.4.0,<8",
39
39
  ]
40
- version = "1.0.0.dev0"
40
+ version = "1.0.0.dev2"
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.dev0"
8
+ __version__ = "1.0.0.dev2"
@@ -0,0 +1,170 @@
1
+ """Module runner — invoked by the dial-back orchestrator.
2
+
3
+ Receives the consumer's first input Struct (the query), resolves the
4
+ setup, builds the input/setup models, looks up the tool cache, and
5
+ starts the module via the job manager. Each module output goes to the
6
+ task's Redis Stream via the ``_on_output`` callback so the gateway's
7
+ ``_consume_from_redis`` can drain it back to the consumer.
8
+
9
+ This used to live inline in ``TaskDispatcher._handle_dispatch``. The
10
+ dispatcher (separate XADD/XREAD bus) is gone in embedded mode — the
11
+ dial-back BiDi handler is the sole orchestrator and calls into here
12
+ directly when the consumer's first reply lands.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import asyncio
18
+ import contextlib
19
+ import time
20
+ from typing import TYPE_CHECKING, Any
21
+
22
+ from google.protobuf import json_format, struct_pb2
23
+
24
+ from digitalkin.core.profiling.step_timer import StepTimer
25
+ from digitalkin.core.profiling.task_profiler import ProfilerMode, TaskProfiler
26
+ from digitalkin.grpc_servers.stream_error_codes import StreamErrorCode
27
+ from digitalkin.logger import logger
28
+ from digitalkin.models.settings.profiling import ProfilingSettings
29
+
30
+ # Singleton — read profiler env once at import.
31
+ _PROFILING = ProfilingSettings()
32
+
33
+ if TYPE_CHECKING:
34
+ from collections.abc import Awaitable, Callable
35
+
36
+ from digitalkin.core.task_manager.redis.redis_client import RedisClient
37
+ from digitalkin.grpc_servers.module_servicer import ModuleServicer
38
+
39
+
40
+ class ModuleRunner:
41
+ """Run one task end-to-end: setup → module instance → output drain.
42
+
43
+ Stateless aside from the injected ``servicer`` and ``redis_client``;
44
+ one instance is shared per ``GatewayServicer`` (created in
45
+ ``module_server._register_gateway_servicer`` and passed to the
46
+ gateway).
47
+ """
48
+
49
+ _redis_client: RedisClient
50
+ _servicer: ModuleServicer
51
+
52
+ def __init__(self, redis_client: RedisClient, servicer: ModuleServicer) -> None:
53
+ """Initialize the runner.
54
+
55
+ Args:
56
+ redis_client: Redis used to write module outputs to the task stream.
57
+ servicer: ModuleServicer for setup resolution and job management.
58
+ """
59
+ self._redis_client = redis_client
60
+ self._servicer = servicer
61
+
62
+ async def run(
63
+ self,
64
+ query: struct_pb2.Struct,
65
+ *,
66
+ task_id: str,
67
+ setup_id: str,
68
+ mission_id: str,
69
+ on_fatal: Callable[[str, str], Awaitable[None]],
70
+ ) -> None:
71
+ """Execute one module task to completion.
72
+
73
+ Args:
74
+ query: The first input Struct received from the consumer
75
+ (delivered via the dial-back BiDi).
76
+ task_id: Task identifier; output stream is ``task:{task_id}:stream``.
77
+ setup_id: Setup identifier (used for setup resolution + tool cache).
78
+ mission_id: Mission identifier (carried for logging context).
79
+ on_fatal: Async callback invoked on any unhandled exception
80
+ with ``(StreamErrorCode value, message)``. The caller is
81
+ responsible for emitting the corresponding ``stream.error``
82
+ + EOS to the task's Redis stream.
83
+ """
84
+ log_extra = {"task_id": task_id, "setup_id": setup_id, "mission_id": mission_id}
85
+ stream_key = f"task:{task_id}:stream"
86
+ timer = StepTimer()
87
+
88
+ # Per-task profiler — zero-cost when DIGITALKIN_PROFILER=none.
89
+ # Runs over the whole module lifecycle so the saved profile shows
90
+ # setup/init/run/output broken down with line-level resolution.
91
+ profiler_mode = ProfilerMode(_PROFILING.profiler) if _PROFILING.profiler in {p.value for p in ProfilerMode} else ProfilerMode.NONE
92
+ profiler = TaskProfiler(task_id=task_id, mode=profiler_mode, output_dir=_PROFILING.profile_output_dir)
93
+ profiler.start()
94
+
95
+ # Fire the (async) setup resolve immediately so it overlaps with
96
+ # the (sync) input parsing below. Cold-cache _resolve_setup is a
97
+ # gRPC call (50-200ms); the input work below is sub-ms.
98
+ setup_version_task = asyncio.create_task(
99
+ self._servicer._resolve_setup(setup_id, mission_id), # noqa: SLF001
100
+ name=f"resolve_setup_{task_id}",
101
+ )
102
+
103
+ try:
104
+ timer.mark("entry")
105
+
106
+ input_dict = json_format.MessageToDict(query)
107
+ timer.mark("struct_to_dict")
108
+
109
+ input_data = self._servicer.module_class.create_input_model(input_dict)
110
+ timer.mark("pydantic_input")
111
+
112
+ setup_version = await setup_version_task
113
+ timer.mark("setup_resolve")
114
+
115
+ setup_data = await self._servicer.module_class.create_setup_model(setup_version.content)
116
+ timer.mark("setup_model")
117
+
118
+ tool_cache = self._servicer.get_tool_cache(setup_version.setup_id)
119
+ timer.mark("tool_cache_lookup")
120
+
121
+ # L8: measure latency from runner entry to first producer output.
122
+ runner_start_ns = time.perf_counter_ns()
123
+ first_logged = False
124
+
125
+ async def _on_output(output_data: Any) -> None:
126
+ nonlocal first_logged
127
+ data = output_data.model_dump(mode="json")
128
+ if not first_logged:
129
+ elapsed_ms = (time.perf_counter_ns() - runner_start_ns) / 1e6
130
+ logger.info(
131
+ "[lat-audit] producer_first_byte_to_redis: %.1fms task_id=%s",
132
+ elapsed_ms, task_id, extra=log_extra,
133
+ )
134
+ first_logged = True
135
+ if data.get("root", {}).get("protocol") == "stream.end":
136
+ await self._redis_client.xadd(stream_key, {"eos": b"true"})
137
+ await self._redis_client.expire(stream_key, 60)
138
+ return
139
+ s = struct_pb2.Struct()
140
+ s.update(data)
141
+ await self._redis_client.xadd(stream_key, {"pb": s.SerializeToString()})
142
+
143
+ await self._servicer.job_manager.create_module_instance_job(
144
+ input_data,
145
+ setup_data,
146
+ mission_id=mission_id,
147
+ setup_id=setup_version.setup_id,
148
+ setup_version_id=setup_version.id,
149
+ request_metadata={"x-task-id": task_id},
150
+ job_id=task_id,
151
+ tool_cache=tool_cache,
152
+ callback=_on_output,
153
+ )
154
+ timer.mark("create_job")
155
+ timer.log("ModuleRunner", task_id)
156
+
157
+ except Exception as exc:
158
+ logger.exception("ModuleRunner: module job failed", extra=log_extra)
159
+ await on_fatal(
160
+ StreamErrorCode.MODULE_RUNTIME_ERROR.value,
161
+ f"module execution failed: {type(exc).__name__}: {exc}",
162
+ )
163
+ # Cancel the resolve task if it's still in flight (sync work
164
+ # raised before we could await it).
165
+ if not setup_version_task.done():
166
+ setup_version_task.cancel()
167
+ with contextlib.suppress(asyncio.CancelledError, Exception):
168
+ await setup_version_task
169
+ finally:
170
+ profiler.stop()
@@ -64,7 +64,8 @@ BACKPRESSURE_TIMEOUT_S = _gw.backpressure.backpressure_timeout_s
64
64
  DEFAULT_OUTPUT_QUEUE_SIZE = _gw.queue.output_queue_size
65
65
  DEFAULT_INPUT_QUEUE_SIZE = _gw.queue.input_queue_size
66
66
  ENQUEUE_TIMEOUT_S = _gw.queue.enqueue_timeout_s
67
- INPUT_WAIT_TIMEOUT_S = _gw.queue.dispatcher_input_wait_s
67
+ INPUT_WAIT_TIMEOUT_S = _gw.queue.dispatcher_input_wait_s # retired in 2.B; see GatewayQueueSettings
68
+ TOOLKIT_CACHE_TTL_S = _gw.queue.toolkit_cache_ttl_s
68
69
  REDIS_HEALTH_CHECK_TIMEOUT_S = _gw.redis_health_timeout
69
70
 
70
71
  # ══════════════════════════════════════════════════════════════════
@@ -25,7 +25,6 @@ from __future__ import annotations
25
25
  import asyncio
26
26
  import contextlib
27
27
  import json
28
- import time
29
28
  from datetime import datetime, timezone
30
29
  from typing import TYPE_CHECKING, Any
31
30
 
@@ -53,6 +52,7 @@ from digitalkin.services.communication.grpc_communication import (
53
52
  if TYPE_CHECKING:
54
53
  from collections.abc import AsyncGenerator, AsyncIterator
55
54
 
55
+ from digitalkin.core.task_manager.module_runner import ModuleRunner
56
56
  from digitalkin.core.task_manager.redis.redis_client import RedisClient
57
57
  from digitalkin.grpc_servers.utils.circuit_breaker import CircuitBreaker
58
58
 
@@ -67,7 +67,6 @@ class GatewayServicer:
67
67
  _registry: StreamRegistry
68
68
  _redis_client: RedisClient
69
69
  _circuit_breaker: CircuitBreaker | None
70
- _dispatch_key: str
71
70
 
72
71
  @staticmethod
73
72
  def _sentinel(seq: int, task_id: str, protocol: str, **fields: Any) -> Any:
@@ -117,27 +116,30 @@ class GatewayServicer:
117
116
  redis_client: RedisClient,
118
117
  max_streams: int = MAX_STREAMS,
119
118
  circuit_breaker: CircuitBreaker | None = None,
120
- dispatch_key: str = "dispatch:module",
121
119
  cache_handler: Any = None,
122
120
  client_config: Any = None,
121
+ module_runner: ModuleRunner | None = None,
123
122
  ) -> None:
124
123
  """Initialize the gateway servicer.
125
124
 
126
125
  Args:
127
- redis_client: Redis for stream persistence, dispatch, and signals.
126
+ redis_client: Redis for stream persistence and signals.
128
127
  max_streams: Maximum concurrent sessions (cluster-wide with Redis).
129
128
  circuit_breaker: Optional circuit breaker for recording success/failure.
130
- dispatch_key: Redis Stream key for task dispatch to the module.
131
129
  cache_handler: Async callback for cache invalidation signals (from ModuleServer).
132
130
  client_config: ClientConfig for outbound dial-back to consumers
133
- (used by the ``_dial_consumer`` callback flow).
131
+ (used by ``_dial_consumer``).
132
+ module_runner: Orchestrator invoked by ``_dial_consumer`` after the
133
+ consumer's first reply lands. Required in embedded mode; the
134
+ dial-back is the sole entry point for module execution and
135
+ cannot proceed without it.
134
136
  """
135
137
  self._registry = StreamRegistry(redis_client, max_streams=max_streams)
136
138
  self._circuit_breaker = circuit_breaker
137
139
  self._redis_client = redis_client
138
- self._dispatch_key = dispatch_key
139
140
  self._cache_handler = cache_handler
140
141
  self._client_config = client_config
142
+ self._module_runner = module_runner
141
143
 
142
144
  async def start(self) -> None:
143
145
  """Start the registry reaper."""
@@ -233,15 +235,11 @@ class GatewayServicer:
233
235
  )
234
236
  timer.mark("xadd_stream_start")
235
237
 
236
- # Start module in background
237
- session._forward_task = asyncio.create_task( # noqa: SLF001
238
- self._start_module(session, request),
239
- name=f"start_module_{task_id}",
240
- )
241
- timer.mark("schedule_start_module")
242
-
243
238
  # Server-initiated dial-back to consumer (mandatory; address
244
- # validated at the top of this method).
239
+ # validated at the top of this method). The dial-back IS the
240
+ # dispatcher: it opens the BiDi, receives the consumer's first
241
+ # reply (the query), and runs the module via ``ModuleRunner.run``.
242
+ # No second background task — there is no separate dispatch flow.
245
243
  logger.info("→ Dial-back scheduled to consumer %s", client_address, extra=log_extra)
246
244
  # F1.7 (deferred): track this task in a servicer-level set with
247
245
  # add_done_callback so it survives strong-reference GC and
@@ -330,47 +328,6 @@ class GatewayServicer:
330
328
  extra=log_extra,
331
329
  )
332
330
 
333
- async def _start_module(self, session: StreamSession, request: Any) -> None:
334
- """Dispatch module execution via Redis.
335
-
336
- XADDs task spec to the dispatch stream. The TaskDispatcher picks
337
- it up, runs the module, and writes output to the proto stream.
338
- Gateway reads output via ProtoStreamReader in Stream.
339
-
340
- Module input arrives via upstream ``StreamClient.data`` messages
341
- (handled by ``_read_consumer_upstream``), not from the dispatch entry.
342
-
343
- Args:
344
- session: The stream session.
345
- request: The StartStreamRequest.
346
- """
347
- log_extra = {
348
- "task_id": session.task_id,
349
- "setup_id": request.setup_id,
350
- "mission_id": request.mission_id,
351
- }
352
- try:
353
- await self._redis_client.xadd(
354
- self._dispatch_key,
355
- {
356
- "task_id": session.task_id,
357
- "ts_ns": str(time.perf_counter_ns()),
358
- "setup_id": request.setup_id,
359
- "mission_id": request.mission_id,
360
- },
361
- )
362
- self._cb_success()
363
- logger.debug("Task dispatched to Redis", extra=log_extra)
364
- except RedisError as exc:
365
- self._cb_failure()
366
- logger.exception("Task dispatch XADD failed", extra=log_extra)
367
- await self._emit_fatal_to_redis(
368
- session.task_id,
369
- code=StreamErrorCode.DISPATCH_UNAVAILABLE.value,
370
- message=f"failed to dispatch task: {type(exc).__name__}",
371
- log_extra=log_extra,
372
- )
373
-
374
331
  async def Stream( # noqa: C901, PLR0912
375
332
  self,
376
333
  request_iterator: AsyncIterator,
@@ -704,6 +661,9 @@ class GatewayServicer:
704
661
  data=srv_msg.data,
705
662
  )
706
663
 
664
+ async def _runner_fatal(code: str, message: str) -> None:
665
+ await self._emit_fatal_to_redis(task_id, code=code, message=message, log_extra=log_extra)
666
+
707
667
  try:
708
668
  logger.info(
709
669
  "→ Opening BiDi to consumer %s (sending stream.init)",
@@ -715,14 +675,36 @@ class GatewayServicer:
715
675
  async for upstream in responses:
716
676
  if not (upstream.data and len(upstream.data.fields) > 0):
717
677
  continue
718
- await session.enqueue_input({"_proto": upstream.data})
719
678
  if first:
720
679
  logger.info(
721
- "← First consumer reply received — query enqueued, output drain unblocked",
680
+ "← First consumer reply received — starting module runner",
722
681
  extra=log_extra,
723
682
  )
683
+ if self._module_runner is None:
684
+ await self._emit_fatal_to_redis(
685
+ task_id,
686
+ code=StreamErrorCode.DIAL_BACK_INTERNAL.value,
687
+ message="gateway has no ModuleRunner configured",
688
+ log_extra=log_extra,
689
+ )
690
+ output_started.set()
691
+ return
692
+ asyncio.create_task( # noqa: RUF006 — runner runs to completion in background; tracked via stream.end EOS
693
+ self._module_runner.run(
694
+ upstream.data,
695
+ task_id=task_id,
696
+ setup_id=setup_id,
697
+ mission_id=mission_id,
698
+ on_fatal=_runner_fatal,
699
+ ),
700
+ name=f"module_runner_{task_id}",
701
+ )
724
702
  output_started.set()
725
703
  first = False
704
+ continue
705
+ # Follow-up multi-turn input: enqueue for any module that
706
+ # consumes session.input_queue mid-run.
707
+ await session.enqueue_input({"_proto": upstream.data})
726
708
  except grpc.aio.AioRpcError as exc:
727
709
  code_name = exc.code().name
728
710
  details = exc.details() or ""
@@ -62,7 +62,6 @@ class ModuleServer(BaseServer):
62
62
  self.registry: RegistryStrategy | None = None
63
63
  self.module_servicer: ModuleServicer | None = None
64
64
  self._gateway_servicer: GatewayServicer | None = None
65
- self._task_dispatcher: Any = None
66
65
 
67
66
  self._prepare_registry_config()
68
67
 
@@ -95,10 +94,12 @@ class ModuleServer(BaseServer):
95
94
  self._register_gateway_servicer()
96
95
 
97
96
  def _register_gateway_servicer(self) -> None:
98
- """Register the embedded GatewayServicer and TaskDispatcher.
97
+ """Register the embedded GatewayServicer.
99
98
 
100
- Gateway dispatches tasks via Redis XADD. TaskDispatcher picks them
101
- up via XREAD and runs modules through ModuleServicer's job manager.
99
+ The dial-back is the sole orchestrator: when a consumer dials in
100
+ and sends its first reply, the gateway's ``_dial_consumer`` calls
101
+ the injected ``ModuleRunner`` directly. There is no separate
102
+ dispatcher process, queue, or Redis stream for dispatch.
102
103
 
103
104
  Raises:
104
105
  RuntimeError: If DIGITALKIN_REDIS_URL is not set.
@@ -109,43 +110,26 @@ class ModuleServer(BaseServer):
109
110
  raise RuntimeError(msg)
110
111
 
111
112
  redis_client = RedisClient(redis_url)
112
- module_id = self.module_class.get_module_id()
113
- dispatch_key = f"dispatch:{module_id}"
113
+
114
+ from digitalkin.core.task_manager.module_runner import ModuleRunner
115
+
116
+ module_runner = ModuleRunner(redis_client=redis_client, servicer=self.module_servicer)
114
117
 
115
118
  self._gateway_servicer = GatewayServicer(
116
119
  redis_client=redis_client,
117
120
  circuit_breaker=self._gateway_circuit_breaker,
118
- dispatch_key=dispatch_key,
119
121
  cache_handler=self._handle_cache_invalidation,
120
- # Used by `_dial_consumer` to dial back to consumer-side
121
- # GatewayService.Stream when the client opts in via
122
- # `x-client-address` metadata.
123
122
  client_config=self.client_config,
123
+ module_runner=module_runner,
124
124
  )
125
125
 
126
126
  self.register_servicer(
127
127
  self._gateway_servicer,
128
128
  gateway_service_pb2_grpc.add_GatewayServiceServicer_to_server,
129
- # Pass DESCRIPTOR (not just names) so gateway_service_pb2 is
130
- # registered into Default() pool — required for reflection
131
- # describe of this service's symbols.
132
129
  service_descriptor=gateway_service_pb2.DESCRIPTOR,
133
130
  )
134
131
 
135
- # TaskDispatcher reads from the same dispatch stream
136
- from digitalkin.core.task_manager.task_dispatcher import TaskDispatcher
137
-
138
- self._task_dispatcher = TaskDispatcher(
139
- redis_client=redis_client,
140
- servicer=self.module_servicer,
141
- dispatch_key=dispatch_key,
142
- # Registry lookup lets the dispatcher pull the first module
143
- # input from session.input_queue (fed by the client's first
144
- # StreamClient.data) instead of from the dispatch entry.
145
- registry=self._gateway_servicer._registry, # noqa: SLF001
146
- )
147
-
148
- logger.info("GatewayServicer + TaskDispatcher registered (Redis: %s, dispatch: %s)", redis_url, dispatch_key)
132
+ logger.info("GatewayServicer + ModuleRunner registered (Redis: %s)", redis_url)
149
133
 
150
134
  async def _handle_cache_invalidation(self, action: str) -> None:
151
135
  """Dispatch cache invalidation by action name. Isolated from module lifecycle.
@@ -284,9 +268,6 @@ class ModuleServer(BaseServer):
284
268
  if self._gateway_servicer is not None:
285
269
  await self._gateway_servicer.start()
286
270
 
287
- if self._task_dispatcher is not None:
288
- await self._task_dispatcher.start()
289
-
290
271
  if self.client_config is not None:
291
272
  await self._init_and_register()
292
273
 
@@ -327,12 +308,6 @@ class ModuleServer(BaseServer):
327
308
  except Exception:
328
309
  logger.exception("Failed to close registry")
329
310
 
330
- if self._task_dispatcher is not None:
331
- try:
332
- await self._task_dispatcher.stop()
333
- except Exception:
334
- logger.exception("Failed to stop task dispatcher")
335
-
336
311
  if self._gateway_servicer is not None:
337
312
  try:
338
313
  await self._gateway_servicer.stop()
@@ -2,6 +2,7 @@
2
2
 
3
3
  import asyncio
4
4
  import os
5
+ import time
5
6
  from argparse import ArgumentParser, Namespace
6
7
  from collections.abc import AsyncGenerator
7
8
  from typing import Any, cast
@@ -18,6 +19,7 @@ from pydantic import ValidationError
18
19
 
19
20
  from digitalkin.core.job_manager.base_job_manager import BaseJobManager
20
21
  from digitalkin.core.job_manager.single_job_manager import SingleJobManager
22
+ from digitalkin.grpc_servers.gateway_constants import TOOLKIT_CACHE_TTL_S
21
23
  from digitalkin.grpc_servers.utils.exceptions import ServerError, ServicerError
22
24
  from digitalkin.logger import logger
23
25
  from digitalkin.models.module.module import ModuleCodeModel, ModuleStatus
@@ -46,7 +48,11 @@ class ModuleServicer(module_service_pb2_grpc.ModuleServiceServicer, ArgParser):
46
48
  setup: SetupStrategy
47
49
  job_manager: BaseJobManager
48
50
  _registry_cache: RegistryStrategy | None
49
- _tool_cache_by_setup: dict[str, Any]
51
+ # Maps setup_id -> (tool_cache, expires_at_perf_counter_ns).
52
+ # TTL'd so a slow-changing tool definition still gets refreshed
53
+ # without a SendSignal/INVALIDATE_TOOLS in the loop. The signal
54
+ # path (`invalidate_tool_cache`) bypasses TTL with a full clear.
55
+ _tool_cache_by_setup: dict[str, tuple[Any, float]]
50
56
  _communication_cache: Any
51
57
 
52
58
  def _add_parser_args(self, parser: ArgumentParser) -> None:
@@ -123,6 +129,37 @@ class ModuleServicer(module_service_pb2_grpc.ModuleServiceServicer, ArgParser):
123
129
  """Clear tool cache. Next request re-resolves tool definitions."""
124
130
  self._tool_cache_by_setup.clear()
125
131
 
132
+ def get_tool_cache(self, setup_id: str) -> Any | None:
133
+ """TTL'd lookup. Returns None if missing or expired (the caller
134
+ is expected to recompute and call ``set_tool_cache``).
135
+
136
+ Args:
137
+ setup_id: Setup identifier.
138
+
139
+ Returns:
140
+ Cached tool definition object, or None on miss/expiry.
141
+ """
142
+ entry = self._tool_cache_by_setup.get(setup_id)
143
+ if entry is None:
144
+ return None
145
+ value, expires_at = entry
146
+ if time.monotonic() >= expires_at:
147
+ self._tool_cache_by_setup.pop(setup_id, None)
148
+ return None
149
+ return value
150
+
151
+ def set_tool_cache(self, setup_id: str, value: Any) -> None:
152
+ """Insert ``value`` with TTL ``TOOLKIT_CACHE_TTL_S``.
153
+
154
+ Args:
155
+ setup_id: Setup identifier.
156
+ value: Tool definition object to cache.
157
+ """
158
+ if len(self._tool_cache_by_setup) >= self._setup_cache_max:
159
+ oldest_key = next(iter(self._tool_cache_by_setup))
160
+ del self._tool_cache_by_setup[oldest_key]
161
+ self._tool_cache_by_setup[setup_id] = (value, time.monotonic() + TOOLKIT_CACHE_TTL_S)
162
+
126
163
  def _get_registry(self) -> RegistryStrategy | None:
127
164
  """Get a cached registry instance if configured.
128
165
 
@@ -516,14 +553,11 @@ class ModuleServicer(module_service_pb2_grpc.ModuleServiceServicer, ArgParser):
516
553
  str(k): str(v) for k, v in cast("list[tuple[str, str]]", context.invocation_metadata() or ())
517
554
  }
518
555
 
519
- # Resolve tool cache (shared across requests with same setup_id)
520
- tool_cache = self._tool_cache_by_setup.get(setup_version.setup_id)
556
+ # Resolve tool cache (shared across requests with same setup_id, TTL'd)
557
+ tool_cache = self.get_tool_cache(setup_version.setup_id)
521
558
  if tool_cache is None and isinstance(setup_data, SetupModel):
522
559
  tool_cache = await setup_data.build_tool_cache(self._get_registry(), self._get_communication())
523
- if len(self._tool_cache_by_setup) >= self._setup_cache_max:
524
- oldest_key = next(iter(self._tool_cache_by_setup))
525
- del self._tool_cache_by_setup[oldest_key]
526
- self._tool_cache_by_setup[setup_version.setup_id] = tool_cache
560
+ self.set_tool_cache(setup_version.setup_id, tool_cache)
527
561
 
528
562
  # create a task to run the module in background
529
563
  logger.debug(
@@ -39,10 +39,17 @@ class GatewayQueueSettings(BaseSettings):
39
39
  dispatcher_input_wait_s: float = Field(
40
40
  default=60.0,
41
41
  description=(
42
- "Max seconds the dispatcher waits for the consumer's first input "
43
- "(via session.input_queue) before emitting INPUT_WAIT_TIMEOUT. "
44
- "Must stay below the dial-back BiDi ceiling (300s) so the dispatcher "
45
- "times out first with a meaningful error code."
42
+ "Retired in Phase 2.B (the dial-back is the orchestrator). "
43
+ "Kept for forward-compat / external readers. No effect."
44
+ ),
45
+ )
46
+ toolkit_cache_ttl_s: float = Field(
47
+ default=600.0,
48
+ description=(
49
+ "TTL for the per-setup tool cache "
50
+ "(``ModuleServicer._tool_cache_by_setup``). Entries older than "
51
+ "this are recomputed on next lookup. The INVALIDATE_TOOLS "
52
+ "SendSignal flushes the whole cache regardless of TTL."
46
53
  ),
47
54
  )
48
55
 
@@ -35,9 +35,13 @@ from digitalkin.logger import logger
35
35
  from digitalkin.models.settings.consumer import ConsumerSettings
36
36
  from digitalkin.models.settings.server.grpc import GrpcServerSettings
37
37
 
38
- # Singleton — read env vars once at import. Field defaults below pull
39
- # from this so any caller building ConsumerConfig() picks up env overrides.
38
+ # Singletons — read env vars once at import. Field defaults below pull
39
+ # from `_CONSUMER_DEFAULTS` so any caller building `ConsumerConfig()`
40
+ # picks up env overrides; `_GRPC_SERVER_OPTIONS` is reused by every
41
+ # `GatewayConsumer` instance so we don't re-instantiate the settings
42
+ # on every consumer build (L5).
40
43
  _CONSUMER_DEFAULTS = ConsumerSettings()
44
+ _GRPC_SERVER_OPTIONS = GrpcServerSettings().options
41
45
 
42
46
  if TYPE_CHECKING:
43
47
  from collections.abc import AsyncGenerator, AsyncIterator
@@ -179,7 +183,7 @@ class GatewayConsumer:
179
183
  self._stub: gateway_service_pb2_grpc.GatewayServiceStub | None = None
180
184
  self._server: grpc.aio.Server | None = host_server
181
185
  self._registry: dict[str, _TaskHandle] = {}
182
- self._grpc_options = GrpcServerSettings().options
186
+ self._grpc_options = _GRPC_SERVER_OPTIONS
183
187
 
184
188
  @classmethod
185
189
  def standalone(cls, config: ConsumerConfig) -> GatewayConsumer:
@@ -6,12 +6,14 @@ the Service Provider's Registry service.
6
6
 
7
7
  from typing import Any
8
8
 
9
+ import grpc
9
10
  from agentic_mesh_protocol.registry.v1 import (
10
11
  registry_enums_pb2,
11
12
  registry_models_pb2,
12
13
  registry_requests_pb2,
13
14
  registry_service_pb2_grpc,
14
15
  )
16
+ from grpc_health.v1 import health_pb2, health_pb2_grpc
15
17
 
16
18
  from digitalkin.grpc_servers.utils.exceptions import ServerError
17
19
  from digitalkin.grpc_servers.utils.grpc_client_wrapper import GrpcClientWrapper
@@ -59,27 +61,23 @@ class GrpcRegistry(RegistryStrategy, GrpcClientWrapper, GrpcErrorHandlerMixin):
59
61
  logger.debug("Channel client 'Registry' initialized successfully")
60
62
 
61
63
  async def wait_for_ready(self, timeout: float = 1.0) -> bool:
62
- """Ping the registry by issuing a real GetModule RPC.
63
-
64
- Any gRPC response (including NOT_FOUND) means the server is alive.
65
- Only connection failure (UNAVAILABLE/DEADLINE_EXCEEDED after retries)
66
- returns False.
64
+ """Probe the registry via the standard gRPC Health Check service.
67
65
 
68
66
  Args:
69
67
  timeout: Max seconds for the round-trip.
70
68
 
71
69
  Returns:
72
- True if the server responded, False if unreachable.
70
+ True if the server responded SERVING, False otherwise.
73
71
  """
72
+ health_stub = health_pb2_grpc.HealthStub(self._channel)
74
73
  try:
75
- await self.exec_grpc_query(
76
- "GetModule",
77
- registry_requests_pb2.GetModuleRequest(module_id="__ping__"),
74
+ response = await health_stub.Check(
75
+ health_pb2.HealthCheckRequest(service=""),
78
76
  timeout=timeout,
79
77
  )
80
- except ServerError as e:
81
- return "UNAVAILABLE" not in str(e) and "DEADLINE_EXCEEDED" not in str(e)
82
- return True
78
+ except grpc.aio.AioRpcError:
79
+ return False
80
+ return response.status == health_pb2.HealthCheckResponse.SERVING
83
81
 
84
82
  @staticmethod
85
83
  def _proto_to_module_info(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: digitalkin
3
- Version: 1.0.0.dev0
3
+ Version: 1.0.0.dev2
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