digitalkin 1.0.5.dev3__tar.gz → 1.0.5.dev4__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 (270) hide show
  1. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/PKG-INFO +1 -1
  2. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/pyproject.toml +1 -1
  3. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/__version__.py +1 -1
  4. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/community/agno/agno_adapter.py +20 -0
  5. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/community/agno/hitl.py +27 -5
  6. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/community/agno/module_toolkit.py +21 -4
  7. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/core/task_manager/module_runner.py +40 -2
  8. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/mixins/agui_mixin.py +175 -9
  9. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/models/module/module_context.py +6 -1
  10. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/models/module/utility.py +6 -0
  11. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/models/settings/module.py +8 -0
  12. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/modules/_base_module.py +20 -4
  13. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/services/communication/exceptions.py +4 -1
  14. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/services/communication/grpc_communication.py +12 -9
  15. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin.egg-info/PKG-INFO +1 -1
  16. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/LICENSE +0 -0
  17. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/README.md +0 -0
  18. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/examples/base_server/__init__.py +0 -0
  19. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/examples/base_server/mock/__init__.py +0 -0
  20. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/examples/base_server/mock/mock_pb2.py +0 -0
  21. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/examples/base_server/mock/mock_pb2_grpc.py +0 -0
  22. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/examples/base_server/server_async_insecure.py +0 -0
  23. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/examples/base_server/server_async_secure.py +0 -0
  24. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/examples/base_server/server_sync_insecure.py +0 -0
  25. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/examples/base_server/server_sync_secure.py +0 -0
  26. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/examples/bench_module/__init__.py +0 -0
  27. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/examples/bench_module/echo_module.py +0 -0
  28. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/examples/bench_module/models/__init__.py +0 -0
  29. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/examples/bench_module/models/input.py +0 -0
  30. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/examples/bench_module/models/output.py +0 -0
  31. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/examples/bench_module/models/secret.py +0 -0
  32. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/examples/bench_module/models/setup.py +0 -0
  33. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/examples/bench_module/server.py +0 -0
  34. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/examples/bench_module/triggers/__init__.py +0 -0
  35. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/examples/bench_module/triggers/message_trigger.py +0 -0
  36. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/examples/modules/__init__.py +0 -0
  37. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/examples/modules/archetype_with_tools_module.py +0 -0
  38. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/examples/modules/cpu_intensive_module.py +0 -0
  39. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/examples/modules/dynamic_setup_module.py +0 -0
  40. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/examples/modules/minimal_llm_module.py +0 -0
  41. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/examples/modules/text_transform_module.py +0 -0
  42. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/examples/monitoring/digitalkin_observability/__init__.py +0 -0
  43. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/examples/monitoring/digitalkin_observability/http_server.py +0 -0
  44. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/examples/monitoring/digitalkin_observability/interceptors.py +0 -0
  45. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/examples/monitoring/digitalkin_observability/metrics.py +0 -0
  46. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/examples/monitoring/digitalkin_observability/prometheus.py +0 -0
  47. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/examples/monitoring/tests/test_metrics.py +0 -0
  48. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/examples/redis_demo/client.py +0 -0
  49. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/examples/redis_demo/echo_module.py +0 -0
  50. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/examples/redis_demo/models/__init__.py +0 -0
  51. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/examples/redis_demo/models/input.py +0 -0
  52. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/examples/redis_demo/models/output.py +0 -0
  53. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/examples/redis_demo/models/secret.py +0 -0
  54. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/examples/redis_demo/models/setup.py +0 -0
  55. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/examples/redis_demo/server.py +0 -0
  56. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/examples/redis_demo/triggers/__init__.py +0 -0
  57. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/examples/redis_demo/triggers/message_trigger.py +0 -0
  58. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/examples/services/filesystem_module.py +0 -0
  59. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/examples/services/storage_module.py +0 -0
  60. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/setup.cfg +0 -0
  61. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/__init__.py +0 -0
  62. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/community/__init__.py +0 -0
  63. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/community/agno/__init__.py +0 -0
  64. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/community/agno/agui_tools.py +0 -0
  65. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/community/agno/models.py +0 -0
  66. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/community/agno/toolkits/__init__.py +0 -0
  67. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/community/agno/toolkits/base.py +0 -0
  68. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/community/agno/toolkits/chat_history.py +0 -0
  69. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/community/agno/toolkits/defaults.py +0 -0
  70. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/community/agno/toolkits/registry/__init__.py +0 -0
  71. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/community/agno/toolkits/registry/action.py +0 -0
  72. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/community/agno/toolkits/registry/base.py +0 -0
  73. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/community/agno/toolkits/registry/kins/__init__.py +0 -0
  74. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/community/agno/toolkits/registry/kins/action.py +0 -0
  75. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/community/agno/toolkits/registry/kins/kit.py +0 -0
  76. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/community/agno/toolkits/registry/loader/__init__.py +0 -0
  77. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/community/agno/toolkits/registry/loader/action.py +0 -0
  78. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/community/agno/toolkits/registry/loader/kit.py +0 -0
  79. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/community/agno/toolkits/registry/services/__init__.py +0 -0
  80. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/community/agno/toolkits/registry/services/action.py +0 -0
  81. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/community/agno/toolkits/registry/services/kit.py +0 -0
  82. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/community/agno/toolkits/registry/tools/__init__.py +0 -0
  83. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/community/agno/toolkits/registry/tools/action.py +0 -0
  84. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/community/agno/toolkits/registry/tools/kit.py +0 -0
  85. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/community/agno/toolkits/user_profile.py +0 -0
  86. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/core/__init__.py +0 -0
  87. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/core/common/__init__.py +0 -0
  88. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/core/common/factories.py +0 -0
  89. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/core/exceptions.py +0 -0
  90. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/core/job_manager/__init__.py +0 -0
  91. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/core/job_manager/base_job_manager.py +0 -0
  92. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/core/job_manager/single_job_manager.py +0 -0
  93. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/core/profiling/__init__.py +0 -0
  94. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/core/profiling/step_timer.py +0 -0
  95. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/core/profiling/task_profiler.py +0 -0
  96. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/core/resilience/__init__.py +0 -0
  97. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/core/resilience/bulkhead.py +0 -0
  98. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/core/resilience/task_supervisor.py +0 -0
  99. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/core/task_manager/__init__.py +0 -0
  100. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/core/task_manager/base_task_manager.py +0 -0
  101. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/core/task_manager/local_task_manager.py +0 -0
  102. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/core/task_manager/redis/__init__.py +0 -0
  103. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/core/task_manager/redis/proto_streams.py +0 -0
  104. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/core/task_manager/redis/redis_client.py +0 -0
  105. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/core/task_manager/redis/redis_idempotency.py +0 -0
  106. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/core/task_manager/redis/redis_signal.py +0 -0
  107. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/core/task_manager/remote_task_manager.py +0 -0
  108. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/core/task_manager/task_executor.py +0 -0
  109. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/core/task_manager/task_session.py +0 -0
  110. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/exceptions.py +0 -0
  111. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/grpc_servers/__init__.py +0 -0
  112. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/grpc_servers/_base_server.py +0 -0
  113. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/grpc_servers/exceptions.py +0 -0
  114. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/grpc_servers/gateway_servicer.py +0 -0
  115. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/grpc_servers/interceptors/__init__.py +0 -0
  116. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/grpc_servers/interceptors/permission.py +0 -0
  117. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/grpc_servers/interceptors/request_ids.py +0 -0
  118. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/grpc_servers/m2m_call_registry.py +0 -0
  119. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/grpc_servers/module_server.py +0 -0
  120. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/grpc_servers/module_servicer.py +0 -0
  121. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/grpc_servers/stream_registry.py +0 -0
  122. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/grpc_servers/stream_session.py +0 -0
  123. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/grpc_servers/utils/__init__.py +0 -0
  124. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/grpc_servers/utils/circuit_breaker.py +0 -0
  125. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/grpc_servers/utils/grpc_client_wrapper.py +0 -0
  126. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/grpc_servers/utils/grpc_error_handler.py +0 -0
  127. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/grpc_servers/utils/utility_schema_extender.py +0 -0
  128. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/grpc_servers/utils/validators.py +0 -0
  129. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/logger.py +0 -0
  130. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/mixins/__init__.py +0 -0
  131. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/mixins/base_mixin.py +0 -0
  132. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/mixins/cost_mixin.py +0 -0
  133. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/mixins/file_history_mixin.py +0 -0
  134. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/mixins/filesystem_mixin.py +0 -0
  135. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/mixins/logger_mixin.py +0 -0
  136. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/mixins/storage_mixin.py +0 -0
  137. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/models/__init__.py +0 -0
  138. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/models/core/__init__.py +0 -0
  139. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/models/core/job_manager_models.py +0 -0
  140. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/models/core/redis.py +0 -0
  141. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/models/core/task_monitor.py +0 -0
  142. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/models/events/__init__.py +0 -0
  143. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/models/events/agent_events.py +0 -0
  144. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/models/grpc_servers/__init__.py +0 -0
  145. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/models/grpc_servers/circuit_breaker.py +0 -0
  146. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/models/grpc_servers/m2m.py +0 -0
  147. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/models/grpc_servers/models.py +0 -0
  148. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/models/grpc_servers/stream_error_codes.py +0 -0
  149. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/models/grpc_servers/types.py +0 -0
  150. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/models/module/__init__.py +0 -0
  151. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/models/module/ag_ui.py +0 -0
  152. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/models/module/base_types.py +0 -0
  153. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/models/module/knowledge_files.py +0 -0
  154. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/models/module/loaded_tools.py +0 -0
  155. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/models/module/module.py +0 -0
  156. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/models/module/module_types.py +0 -0
  157. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/models/module/request_metadata.py +0 -0
  158. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/models/module/select_schema.py +0 -0
  159. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/models/module/setup_types.py +0 -0
  160. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/models/module/tool_cache.py +0 -0
  161. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/models/module/tool_reference.py +0 -0
  162. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/models/services/__init__.py +0 -0
  163. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/models/services/cost.py +0 -0
  164. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/models/services/filesystem.py +0 -0
  165. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/models/services/registry.py +0 -0
  166. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/models/services/services.py +0 -0
  167. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/models/services/storage.py +0 -0
  168. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/models/settings/__init__.py +0 -0
  169. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/models/settings/certificate.py +0 -0
  170. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/models/settings/client/__init__.py +0 -0
  171. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/models/settings/client/channel.py +0 -0
  172. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/models/settings/client/circuit_breaker.py +0 -0
  173. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/models/settings/client/client.py +0 -0
  174. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/models/settings/client/grpc.py +0 -0
  175. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/models/settings/gateway.py +0 -0
  176. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/models/settings/log.py +0 -0
  177. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/models/settings/profiling.py +0 -0
  178. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/models/settings/queue.py +0 -0
  179. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/models/settings/redis.py +0 -0
  180. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/models/settings/registry.py +0 -0
  181. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/models/settings/resilience.py +0 -0
  182. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/models/settings/server/__init__.py +0 -0
  183. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/models/settings/server/channel.py +0 -0
  184. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/models/settings/server/grpc.py +0 -0
  185. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/models/settings/server/server.py +0 -0
  186. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/models/settings/server/servicer.py +0 -0
  187. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/models/settings/task_manager.py +0 -0
  188. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/models/settings/utils/__init__.py +0 -0
  189. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/models/settings/utils/channel.py +0 -0
  190. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/models/utils/__init__.py +0 -0
  191. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/models/utils/dynamic_schema.py +0 -0
  192. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/modules/__init__.py +0 -0
  193. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/modules/archetype_module.py +0 -0
  194. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/modules/tool_module.py +0 -0
  195. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/modules/trigger_handler.py +0 -0
  196. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/modules/triggers/__init__.py +0 -0
  197. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/modules/triggers/healthcheck_ping_trigger.py +0 -0
  198. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/modules/triggers/healthcheck_services_trigger.py +0 -0
  199. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/modules/triggers/healthcheck_status_trigger.py +0 -0
  200. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/py.typed +0 -0
  201. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/services/__init__.py +0 -0
  202. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/services/base_strategy.py +0 -0
  203. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/services/communication/__init__.py +0 -0
  204. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/services/communication/communication_strategy.py +0 -0
  205. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/services/communication/default_communication.py +0 -0
  206. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/services/cost/__init__.py +0 -0
  207. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/services/cost/cost_strategy.py +0 -0
  208. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/services/cost/default_cost.py +0 -0
  209. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/services/cost/exceptions.py +0 -0
  210. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/services/cost/grpc_cost.py +0 -0
  211. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/services/filesystem/__init__.py +0 -0
  212. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/services/filesystem/default_filesystem.py +0 -0
  213. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/services/filesystem/exceptions.py +0 -0
  214. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/services/filesystem/filesystem_strategy.py +0 -0
  215. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/services/filesystem/grpc_filesystem.py +0 -0
  216. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/services/identity/__init__.py +0 -0
  217. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/services/identity/default_identity.py +0 -0
  218. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/services/identity/identity_strategy.py +0 -0
  219. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/services/registry/__init__.py +0 -0
  220. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/services/registry/default_registry.py +0 -0
  221. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/services/registry/exceptions.py +0 -0
  222. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/services/registry/grpc_registry.py +0 -0
  223. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/services/registry/registry_models.py +0 -0
  224. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/services/registry/registry_strategy.py +0 -0
  225. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/services/secret/__init__.py +0 -0
  226. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/services/secret/default_secret.py +0 -0
  227. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/services/secret/exceptions.py +0 -0
  228. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/services/secret/grpc_secret.py +0 -0
  229. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/services/secret/secret_strategy.py +0 -0
  230. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/services/services_config.py +0 -0
  231. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/services/services_models.py +0 -0
  232. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/services/setup/__init__.py +0 -0
  233. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/services/setup/default_setup.py +0 -0
  234. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/services/setup/exceptions.py +0 -0
  235. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/services/setup/grpc_setup.py +0 -0
  236. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/services/setup/setup_strategy.py +0 -0
  237. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/services/storage/__init__.py +0 -0
  238. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/services/storage/default_storage.py +0 -0
  239. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/services/storage/exceptions.py +0 -0
  240. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/services/storage/grpc_storage.py +0 -0
  241. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/services/storage/storage_strategy.py +0 -0
  242. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/services/task_manager/__init__.py +0 -0
  243. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/services/task_manager/default_task_manager.py +0 -0
  244. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/services/task_manager/exceptions.py +0 -0
  245. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/services/task_manager/redis_task_manager.py +0 -0
  246. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/services/task_manager/task_manager_strategy.py +0 -0
  247. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/services/user_profile/__init__.py +0 -0
  248. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/services/user_profile/default_user_profile.py +0 -0
  249. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/services/user_profile/exceptions.py +0 -0
  250. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/services/user_profile/grpc_user_profile.py +0 -0
  251. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/services/user_profile/user_profile_strategy.py +0 -0
  252. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/utils/__init__.py +0 -0
  253. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/utils/arg_parser.py +0 -0
  254. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/utils/conditional_schema.py +0 -0
  255. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/utils/development_mode_action.py +0 -0
  256. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/utils/dynamic_schema.py +0 -0
  257. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/utils/env_manager.py +0 -0
  258. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/utils/env_template.py +0 -0
  259. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/utils/exceptions.py +0 -0
  260. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/utils/json_structure.py +0 -0
  261. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/utils/llm_ready_schema.py +0 -0
  262. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/utils/package_discover.py +0 -0
  263. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/utils/proto_utils.py +0 -0
  264. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/utils/schema_splitter.py +0 -0
  265. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin/utils/setup_content_validator.py +0 -0
  266. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin.egg-info/SOURCES.txt +0 -0
  267. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin.egg-info/dependency_links.txt +0 -0
  268. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin.egg-info/requires.txt +0 -0
  269. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/src/digitalkin.egg-info/top_level.txt +0 -0
  270. {digitalkin-1.0.5.dev3 → digitalkin-1.0.5.dev4}/tests/test_exceptions.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: digitalkin
3
- Version: 1.0.5.dev3
3
+ Version: 1.0.5.dev4
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
@@ -41,7 +41,7 @@
41
41
  "pydantic>=2.12.4",
42
42
  "redis[hiredis]>=7.4.0,<9",
43
43
  ]
44
- version = "1.0.5.dev3"
44
+ version = "1.0.5.dev4"
45
45
 
46
46
  [project.optional-dependencies]
47
47
  agno = [ "agno>=2.6" ]
@@ -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.5.dev3"
8
+ __version__ = "1.0.5.dev4"
@@ -676,6 +676,26 @@ class AgnoStreamAdapter:
676
676
  self._closed_tool_call_ids.add(tool_call_id)
677
677
 
678
678
  content = agno_event.content
679
+ if tool is not None and tool.tool_call_error:
680
+ # Agno flags a raised tool on the completed event, then sends a tool_call_error
681
+ # that the closed-id check drops — so the failure is surfaced here.
682
+ # TODO(validate): AGUI-TOOL-FAILED-STATUS a raised tool surfaces as ToolCallError, not success
683
+ logger.info(
684
+ "[VALIDATE AGUI-TOOL-FAILED-STATUS] tool %s (id=%s) failed: %s",
685
+ tool.tool_name,
686
+ tool_call_id,
687
+ tool.result,
688
+ )
689
+ return [
690
+ ToolCallErrorEvent(
691
+ event=AgentRunEvent.TOOL_CALL_ERROR,
692
+ tool=tool_info,
693
+ error_message=str(tool.result) if tool.result else None,
694
+ subagent_run_id=self._subagent_of(self._run_key),
695
+ timestamp=timestamp,
696
+ metadata=self._last_metadata,
697
+ )
698
+ ]
679
699
  return [
680
700
  ToolCallCompletedEvent(
681
701
  event=AgentRunEvent.TOOL_CALL_COMPLETED,
@@ -11,6 +11,7 @@ import json
11
11
  import logging
12
12
  from typing import TYPE_CHECKING, Any, ClassVar
13
13
 
14
+ from ag_ui.core.events import Interrupt, RunFinishedInterruptOutcome
14
15
  from pydantic import BaseModel, ConfigDict, Field
15
16
 
16
17
  from digitalkin.community.agno.models import PauseInfo
@@ -268,13 +269,14 @@ class HitlEvents:
268
269
  run_id: str,
269
270
  pending_tool_call_ids: list[str],
270
271
  ) -> None:
271
- """Emit an AG-UI ``RunFinished`` with ``status="awaiting_tool_result"``.
272
+ """Emit an AG-UI ``RunFinished`` with an interrupt outcome.
272
273
 
273
274
  This is the protocol signal telling the front "the run paused on a
274
- client-side tool; execute it and reply with a ``ToolMessage``". It
275
- goes out via ``context.callbacks.send_message`` (bypassing the
276
- standard :class:`~digitalkin.mixins.agui_mixin.AgUiMixin` event
277
- mapping, which has no notion of an "awaiting" status).
275
+ client-side tool; execute it and reply with a ``ToolMessage``". The
276
+ standard ``outcome={type: "interrupt"}`` carries one interrupt per pending
277
+ tool; the legacy ``result.status="awaiting_tool_result"`` is kept for
278
+ existing clients. When an AG-UI run is open, its state first moves the
279
+ tools to ``awaiting_input`` and the run to ``interrupted``.
278
280
 
279
281
  Args:
280
282
  context: Current module context.
@@ -291,11 +293,31 @@ class HitlEvents:
291
293
  AgUiRunFinishedOutput,
292
294
  )
293
295
 
296
+ run = context.agui_run
297
+ tools = run.state["tools"] if run is not None else {}
298
+ interrupts = [
299
+ Interrupt(
300
+ id=tool_call_id,
301
+ reason="tool_call",
302
+ tool_call_id=tool_call_id,
303
+ message=f"Waiting for the result of {tools.get(tool_call_id, {}).get('name') or 'a frontend tool'}",
304
+ )
305
+ for tool_call_id in pending_tool_call_ids
306
+ ]
307
+ if run is not None and run.open:
308
+ run.open = False
309
+ await run.update(
310
+ context,
311
+ *((("tools", tid, "status"), "awaiting_input") for tid in pending_tool_call_ids if tid in tools),
312
+ (("interrupts",), [i.model_dump(mode="json", by_alias=True, exclude_none=True) for i in interrupts]),
313
+ (("run", "status"), "interrupted"),
314
+ )
294
315
  output = AgUiOutput(
295
316
  root=AgUiRunFinishedOutput(
296
317
  event=AgUiRunFinishedEvent(
297
318
  thread_id=thread_id,
298
319
  run_id=run_id,
320
+ outcome=RunFinishedInterruptOutcome(interrupts=interrupts) if interrupts else None,
299
321
  result={
300
322
  "status": _AWAITING_STATUS,
301
323
  "pending_tool_call_ids": pending_tool_call_ids,
@@ -25,6 +25,7 @@ from digitalkin.logger import logger
25
25
  from digitalkin.models.module import ModuleContext
26
26
  from digitalkin.models.module.ag_ui import AgUiCustomEventOutput, AgUiOutput
27
27
  from digitalkin.models.module.tool_cache import ToolDefinition, ToolModuleInfo
28
+ from digitalkin.services.communication.exceptions import ToolCallError
28
29
 
29
30
  # Default timeout for tool calls in seconds
30
31
  DEFAULT_TOOL_TIMEOUT_SECONDS = 300
@@ -265,7 +266,14 @@ class ModuleToolkit(DkToolkit):
265
266
  # callbacks is a dict-driven SimpleNamespace (module_context.py:168);
266
267
  # send_message may legitimately be absent outside a running job.
267
268
  send_message = vars(context.callbacks).get("send_message")
269
+ value = event.get("value")
270
+ url = value.get("url") if isinstance(value, dict) else None
268
271
  if send_message is None:
272
+ logger.warning(
273
+ "Dropped custom event '%s' url=%s: no send_message on the agent context",
274
+ event["name"],
275
+ url,
276
+ )
269
277
  return
270
278
 
271
279
  try:
@@ -275,10 +283,15 @@ class ModuleToolkit(DkToolkit):
275
283
  await send_message(
276
284
  AgUiOutput(
277
285
  root=AgUiCustomEventOutput(
278
- event=AgUiCustomEvent(name=event["name"], value=event.get("value")),
286
+ event=AgUiCustomEvent(name=event["name"], value=value),
279
287
  )
280
288
  )
281
289
  )
290
+ logger.info(
291
+ "Relayed custom event '%s' url=%s to the agent stream",
292
+ event["name"],
293
+ url,
294
+ )
282
295
  except Exception:
283
296
  logger.exception("Failed to relay custom event '%s' to the agent stream", event["name"])
284
297
 
@@ -467,6 +480,10 @@ class ModuleToolkit(DkToolkit):
467
480
  Wraps the SDK module's async generator function into an async function
468
481
  that Agno can consume, with proper error handling and response formatting.
469
482
 
483
+ A failure raises ``ToolCallError`` carrying the same JSON body a success-path caller
484
+ would have read: Agno hands ``str(exc)`` to the model unchanged and flags the call
485
+ ``tool_call_error``, which is what marks the tool failed for the front.
486
+
470
487
  Returns:
471
488
  Async function that calls the SDK tool and returns a JSON result, or a
472
489
  ToolResult when the tool returned images alongside its text output.
@@ -509,14 +526,14 @@ class ModuleToolkit(DkToolkit):
509
526
  duration_ms = round((time.perf_counter() - start_time) * 1000, 2)
510
527
  error_msg = f"Tool '{tool_name}' timed out after {timeout}s"
511
528
  logger.warning("%s task_id=%s", error_msg, task_id)
512
- return handle_failure(tool_name, error_msg, duration_ms)
529
+ raise ToolCallError(handle_failure(tool_name, error_msg, duration_ms))
513
530
 
514
531
  except Exception as e:
515
532
  outcome = "error"
516
533
  duration_ms = round((time.perf_counter() - start_time) * 1000, 2)
517
534
  error_msg = f"Failed to call tool '{tool_name}': {e!s}"
518
535
  logger.warning("%s task_id=%s", error_msg, task_id, exc_info=True)
519
- return handle_failure(tool_name, error_msg, duration_ms)
536
+ raise ToolCallError(handle_failure(tool_name, error_msg, duration_ms))
520
537
 
521
538
  else:
522
539
  call_timer.mark("gen_consume")
@@ -536,7 +553,7 @@ class ModuleToolkit(DkToolkit):
536
553
 
537
554
  outcome = "no_success"
538
555
  error_msg = ModuleToolkit._extract_error_message(error_frame)
539
- return handle_failure(tool_name, error_msg, duration_ms)
556
+ raise ToolCallError(handle_failure(tool_name, error_msg, duration_ms))
540
557
 
541
558
  finally:
542
559
  call_timer.mark("respond")
@@ -21,9 +21,10 @@ from digitalkin.models.core.task_monitor import CancellationReason
21
21
  from digitalkin.models.grpc_servers.stream_error_codes import StreamErrorCode
22
22
  from digitalkin.models.module.base_types import DataModel
23
23
  from digitalkin.models.module.module import ModuleStatus
24
- from digitalkin.models.module.utility import EndOfStreamOutput, StreamCancelledOutput
24
+ from digitalkin.models.module.utility import EndOfStreamOutput, StreamCancelledOutput, StreamHeartbeatOutput
25
25
  from digitalkin.models.services.storage import BaseRole
26
26
  from digitalkin.models.settings.gateway import get_gateway_settings
27
+ from digitalkin.models.settings.module import get_module_settings
27
28
  from digitalkin.models.settings.profiling import ProfilerMode, get_profiling_settings
28
29
  from digitalkin.models.settings.redis import get_redis_settings
29
30
 
@@ -143,9 +144,11 @@ class ModuleRunner:
143
144
  idem_key = f"idem:{task_id}"
144
145
  idem_ttl = get_redis_settings().idem_ttl
145
146
  eos_written = False
147
+ last_write_ns = time.monotonic_ns()
146
148
 
147
149
  async def _on_output(output_data: Any) -> None:
148
- nonlocal first_logged, seq, eos_written
150
+ nonlocal first_logged, seq, eos_written, last_write_ns
151
+ last_write_ns = time.monotonic_ns()
149
152
  data = output_data.model_dump(mode="json")
150
153
  if data.get("root", {}).get("protocol") == "stream.end":
151
154
  t_eos_write_start = time.perf_counter_ns()
@@ -259,6 +262,41 @@ class ModuleRunner:
259
262
  timer.mark("create_job")
260
263
  timer.log("ModuleRunner", task_id)
261
264
 
265
+ async def _heartbeat() -> None:
266
+ interval_s = get_module_settings().heartbeat_interval_s
267
+ while not eos_written:
268
+ # Sleep only the rest of the silence window, so no gap exceeds one interval.
269
+ remaining_s = interval_s - (time.monotonic_ns() - last_write_ns) / 1e9
270
+ if remaining_s > 0:
271
+ await asyncio.sleep(remaining_s)
272
+ continue
273
+ # TODO(validate): TASK-HEARTBEAT a silent running task keeps its stream open
274
+ logger.info("[VALIDATE TASK-HEARTBEAT] task silent for %.0fs; beating", interval_s, extra=log_extra)
275
+ run = module.context.agui_run
276
+ try:
277
+ if run is not None and run.open:
278
+ await run.update(module.context, (("run", "heartbeatAt"), run.now_ms()))
279
+ else:
280
+ await _on_output(
281
+ DataModel[StreamHeartbeatOutput](
282
+ root=StreamHeartbeatOutput(), annotations={"role": BaseRole.SYSTEM}
283
+ )
284
+ )
285
+ except Exception:
286
+ # The beat task is never awaited: log here or the failure is lost.
287
+ logger.exception("ModuleRunner: heartbeat stopped", extra=log_extra)
288
+ return
289
+
290
+ module_task = self._servicer.job_manager.tasks.get(job_id)
291
+ if module_task is None or eos_written:
292
+ logger.debug(
293
+ "ModuleRunner: no heartbeat (task registered=%s)", module_task is not None, extra=log_extra
294
+ )
295
+ else:
296
+ # The task's done-callback holds the only reference and stops the beat with it.
297
+ beat = asyncio.create_task(_heartbeat())
298
+ module_task.add_done_callback(lambda _: beat.cancel())
299
+
262
300
  if await self._redis_client.exists(cancel_key):
263
301
  task = self._servicer.job_manager.tasks.get(job_id)
264
302
  session = self._servicer.job_manager.tasks_sessions.get(job_id)
@@ -3,14 +3,16 @@
3
3
  This mixin provides utilities to convert framework-agnostic agent events
4
4
  into AG-UI protocol events and send them through the module context callbacks.
5
5
 
6
- The mixin is a stateless emitter: it receives events with all necessary info
7
- (including IDs) and emits the corresponding AG-UI protocol events.
8
- All state management (ID generation, lifecycle tracking) belongs in the adapter layer.
6
+ The mixin receives events with all necessary info (including IDs) and emits the
7
+ corresponding AG-UI protocol events. Event-sequence bookkeeping belongs in the adapter;
8
+ the only state kept here is the SDK-owned pipeline view (``AgUiRunState``) mirrored to
9
+ the front as ``STATE_SNAPSHOT`` + ``STATE_DELTA``. See ``docs/agui_events.md``.
9
10
  """
10
11
 
11
12
  from __future__ import annotations
12
13
 
13
14
  import json
15
+ import time
14
16
  import uuid
15
17
  from typing import TYPE_CHECKING, Any, ClassVar
16
18
 
@@ -23,6 +25,7 @@ from ag_ui.core.events import ReasoningStartEvent as AgUiReasoningStartEvent
23
25
  from ag_ui.core.events import RunErrorEvent as AgUiRunErrorEvent
24
26
  from ag_ui.core.events import RunFinishedEvent as AgUiRunFinishedEvent
25
27
  from ag_ui.core.events import RunStartedEvent as AgUiRunStartedEvent
28
+ from ag_ui.core.events import StateDeltaEvent, StateSnapshotEvent
26
29
  from ag_ui.core.events import SubagentErrorEvent as AgUiSubagentErrorEvent
27
30
  from ag_ui.core.events import SubagentFinishedEvent as AgUiSubagentFinishedEvent
28
31
  from ag_ui.core.events import SubagentStartedEvent as AgUiSubagentStartedEvent
@@ -35,6 +38,7 @@ from ag_ui.core.events import ToolCallResultEvent as AgUiToolCallResultEvent
35
38
  from ag_ui.core.events import ToolCallStartEvent as AgUiToolCallStartEvent
36
39
  from pydantic import BaseModel
37
40
 
41
+ from digitalkin.logger import logger
38
42
  from digitalkin.models.events import (
39
43
  AgentRunEvent,
40
44
  BaseAgentRunEvent,
@@ -67,6 +71,8 @@ from digitalkin.models.module.ag_ui import (
67
71
  AgUiRunErrorOutput,
68
72
  AgUiRunFinishedOutput,
69
73
  AgUiRunStartedOutput,
74
+ AgUiStateDeltaOutput,
75
+ AgUiStateSnapshotOutput,
70
76
  AgUiSubagentErrorOutput,
71
77
  AgUiSubagentFinishedOutput,
72
78
  AgUiSubagentStartedOutput,
@@ -84,19 +90,110 @@ if TYPE_CHECKING:
84
90
  from digitalkin.models.module.module_context import ModuleContext
85
91
 
86
92
 
93
+ class AgUiRunState:
94
+ """SDK-owned pipeline state of one task's AG-UI run.
95
+
96
+ The front only renders it: ``snapshot`` replaces its state, ``update`` sends the
97
+ matching JSON Patch. Shape: ``run`` (status, error, heartbeatAt), ``tools`` and
98
+ ``subagents`` keyed by id (name, status, startedAt, error), ``interrupts``.
99
+ """
100
+
101
+ def __init__(self) -> None:
102
+ """Start a running run with an empty pipeline."""
103
+ self.open = True
104
+ self.state: dict[str, Any] = {
105
+ "run": {"status": "running", "error": None, "heartbeatAt": None},
106
+ "tools": {},
107
+ "subagents": {},
108
+ "interrupts": [],
109
+ }
110
+
111
+ async def snapshot(self, context: ModuleContext) -> None:
112
+ """Emit the full state as ``STATE_SNAPSHOT``.
113
+
114
+ Args:
115
+ context: Module context whose callbacks carry the event.
116
+ """
117
+ await context.callbacks.send_message(
118
+ AgUiOutput(root=AgUiStateSnapshotOutput(event=StateSnapshotEvent(snapshot=self.state)))
119
+ )
120
+
121
+ async def update(self, context: ModuleContext, *changes: tuple[tuple[str, ...], Any]) -> None:
122
+ """Set each ``(key path, value)`` in the state and emit them as one ``STATE_DELTA``.
123
+
124
+ Args:
125
+ context: Module context whose callbacks carry the event.
126
+ *changes: Key path into the state and the value to set there.
127
+ """
128
+ ops = []
129
+ for keys, value in changes:
130
+ node = self.state
131
+ for key in keys[:-1]:
132
+ node = node[key]
133
+ ops.append({
134
+ "op": "replace" if keys[-1] in node else "add",
135
+ "path": "/" + "/".join(key.replace("~", "~0").replace("/", "~1") for key in keys),
136
+ "value": value,
137
+ })
138
+ node[keys[-1]] = value.copy() if isinstance(value, dict | list) else value
139
+ await context.callbacks.send_message(AgUiOutput(root=AgUiStateDeltaOutput(event=StateDeltaEvent(delta=ops))))
140
+
141
+ async def fail(
142
+ self,
143
+ context: ModuleContext,
144
+ status: str,
145
+ message: str,
146
+ code: str | None,
147
+ **fields: Any,
148
+ ) -> None:
149
+ """Close the run once: record ``status``/``message`` in state, then emit ``RUN_ERROR``.
150
+
151
+ Args:
152
+ context: Module context whose callbacks carry the events.
153
+ status: Terminal run status, ``failed`` or ``cancelled``.
154
+ message: Human-readable error.
155
+ code: ``RUN_ERROR`` code.
156
+ **fields: Extra AG-UI event fields (metadata).
157
+ """
158
+ if not self.open:
159
+ logger.info(
160
+ "AG-UI run already closed (%s); dropping %s RUN_ERROR code=%s message=%s",
161
+ self.state["run"]["status"],
162
+ status,
163
+ code,
164
+ message,
165
+ extra=context.session.current_ids(),
166
+ )
167
+ return
168
+ self.open = False
169
+ await self.update(context, (("run", "status"), status), (("run", "error"), message))
170
+ await context.callbacks.send_message(
171
+ AgUiOutput(root=AgUiRunErrorOutput(event=AgUiRunErrorEvent(message=message, code=code, **fields)))
172
+ )
173
+
174
+ @staticmethod
175
+ def now_ms() -> int:
176
+ """Wall-clock epoch milliseconds, the unit of every ``*At`` field.
177
+
178
+ Returns:
179
+ Current time in epoch milliseconds.
180
+ """
181
+ return time.time_ns() // 1_000_000
182
+
183
+
87
184
  class AgUiMixin:
88
185
  """Mixin for converting agent events to AG-UI protocol and sending them.
89
186
 
90
- This mixin is a stateless emitter: each handler reads IDs from the event
91
- and emits the corresponding AG-UI event(s). The adapter is responsible for
92
- generating IDs and managing event lifecycle (start/complete sequences).
187
+ Each handler reads IDs from the event and emits the corresponding AG-UI event(s),
188
+ plus the ``STATE_DELTA`` that keeps ``context.agui_run`` in sync. The adapter is
189
+ responsible for generating IDs and managing event lifecycle (start/complete sequences).
93
190
 
94
191
  Usage::
95
192
 
96
193
  class MyTrigger(BaseTrigger, AgUiMixin):
97
194
  async def execute(self, context, input_data):
98
195
  async for event in agent.run(input_data.message, stream=True):
99
- await self.agui_send_message(context, event)
196
+ await self.send_message(context, event)
100
197
  """
101
198
 
102
199
  def __init__(self) -> None:
@@ -217,6 +314,8 @@ class AgUiMixin:
217
314
  )
218
315
  )
219
316
  await self._send_agui(context, output)
317
+ context.agui_run = AgUiRunState()
318
+ await context.agui_run.snapshot(context)
220
319
 
221
320
  async def _handle_text_message_started(
222
321
  self,
@@ -285,6 +384,10 @@ class AgUiMixin:
285
384
  event.metadata,
286
385
  extra=context.session.current_ids(),
287
386
  )
387
+ run = context.agui_run
388
+ if run is not None and run.open:
389
+ run.open = False
390
+ await run.update(context, (("run", "status"), "completed"))
288
391
  output = AgUiRunFinishedOutput(
289
392
  event=AgUiRunFinishedEvent(
290
393
  thread_id=self._thread_id,
@@ -299,8 +402,11 @@ class AgUiMixin:
299
402
  context: ModuleContext,
300
403
  event: RunErrorEvent,
301
404
  ) -> None:
302
- """Handle run error event - emit AG-UI RunError."""
405
+ """Handle run error event - mark the run failed and emit AG-UI RunError."""
303
406
  error_msg = event.content or "Agent run failed"
407
+ if context.agui_run is not None:
408
+ await context.agui_run.fail(context, "failed", error_msg, event.error_type, **self._authored(event))
409
+ return
304
410
  output = AgUiRunErrorOutput(
305
411
  event=AgUiRunErrorEvent(
306
412
  message=error_msg,
@@ -326,6 +432,14 @@ class AgUiMixin:
326
432
  )
327
433
  )
328
434
  await self._send_agui(context, output)
435
+ if context.agui_run is not None and event.subagent_run_id:
436
+ await context.agui_run.update(
437
+ context,
438
+ (
439
+ ("subagents", event.subagent_run_id),
440
+ {"name": event.name, "status": "running", "startedAt": AgUiRunState.now_ms(), "error": None},
441
+ ),
442
+ )
329
443
 
330
444
  async def _handle_subagent_finished(
331
445
  self,
@@ -341,6 +455,9 @@ class AgUiMixin:
341
455
  )
342
456
  )
343
457
  await self._send_agui(context, output)
458
+ run = context.agui_run
459
+ if run is not None and event.subagent_run_id is not None and event.subagent_run_id in run.state["subagents"]:
460
+ await run.update(context, (("subagents", event.subagent_run_id, "status"), "completed"))
344
461
 
345
462
  async def _handle_subagent_error(
346
463
  self,
@@ -361,6 +478,13 @@ class AgUiMixin:
361
478
  )
362
479
  )
363
480
  await self._send_agui(context, output)
481
+ run = context.agui_run
482
+ if run is not None and event.subagent_run_id is not None and event.subagent_run_id in run.state["subagents"]:
483
+ await run.update(
484
+ context,
485
+ (("subagents", event.subagent_run_id, "status"), "failed"),
486
+ (("subagents", event.subagent_run_id, "error"), event.message),
487
+ )
364
488
 
365
489
  async def _handle_tool_call_started(
366
490
  self,
@@ -394,6 +518,21 @@ class AgUiMixin:
394
518
  )
395
519
  await self._send_agui(context, args_output)
396
520
 
521
+ if context.agui_run is not None:
522
+ await context.agui_run.update(
523
+ context,
524
+ (
525
+ ("tools", tool_call_id),
526
+ {
527
+ "name": tool.tool_name,
528
+ "status": "running",
529
+ "subagentRunId": event.subagent_run_id,
530
+ "startedAt": AgUiRunState.now_ms(),
531
+ "error": None,
532
+ },
533
+ ),
534
+ )
535
+
397
536
  async def _handle_tool_call_completed(
398
537
  self,
399
538
  context: ModuleContext,
@@ -425,19 +564,46 @@ class AgUiMixin:
425
564
  )
426
565
  await self._send_agui(context, result_output)
427
566
 
567
+ run = context.agui_run
568
+ if run is not None and tool_call_id in run.state["tools"]:
569
+ await run.update(context, (("tools", tool_call_id, "status"), "completed"))
570
+
428
571
  async def _handle_tool_call_error(
429
572
  self,
430
573
  context: ModuleContext,
431
574
  event: ToolCallErrorEvent,
432
575
  ) -> None:
433
- """Handle tool call error event - emit AG-UI ToolCallEnd."""
576
+ """Handle tool call error event - emit AG-UI ToolCallEnd + an error ToolCallResult, mark it failed.
577
+
578
+ ``TOOL_CALL_RESULT`` has no error flag, so the failure is carried by its
579
+ ``{"error": ...}`` content and by ``tools/<id>/status = failed`` in state.
580
+ """
434
581
  tool = event.tool
435
582
  if not tool:
436
583
  return
437
584
 
438
585
  tool_call_id = tool.tool_call_id or str(uuid.uuid4())
586
+ error_msg = event.error_message or "Tool call failed"
439
587
  output = AgUiToolCallEndOutput(event=AgUiToolCallEndEvent(tool_call_id=tool_call_id, **self._authored(event)))
440
588
  await self._send_agui(context, output)
589
+ result_output = AgUiToolCallResultOutput(
590
+ event=AgUiToolCallResultEvent(
591
+ message_id=str(uuid.uuid4()),
592
+ tool_call_id=tool_call_id,
593
+ content=json.dumps({"error": error_msg}),
594
+ role="tool",
595
+ **self._authored(event),
596
+ )
597
+ )
598
+ await self._send_agui(context, result_output)
599
+
600
+ run = context.agui_run
601
+ if run is not None and tool_call_id in run.state["tools"]:
602
+ await run.update(
603
+ context,
604
+ (("tools", tool_call_id, "status"), "failed"),
605
+ (("tools", tool_call_id, "error"), error_msg),
606
+ )
441
607
 
442
608
  async def _handle_reasoning_started(
443
609
  self,
@@ -4,7 +4,7 @@ import asyncio
4
4
  from collections.abc import AsyncGenerator, Callable
5
5
  from datetime import tzinfo
6
6
  from types import SimpleNamespace
7
- from typing import Any
7
+ from typing import TYPE_CHECKING, Any
8
8
  from zoneinfo import ZoneInfo
9
9
 
10
10
  from google.protobuf import json_format
@@ -28,6 +28,9 @@ from digitalkin.services.storage.storage_strategy import StorageStrategy
28
28
  from digitalkin.services.task_manager.task_manager_strategy import TaskManagerStrategy
29
29
  from digitalkin.services.user_profile.user_profile_strategy import UserProfileStrategy
30
30
 
31
+ if TYPE_CHECKING:
32
+ from digitalkin.mixins.agui_mixin import AgUiRunState
33
+
31
34
 
32
35
  class Session(SimpleNamespace):
33
36
  """Session data container with mandatory setup_id and mission_id."""
@@ -116,6 +119,7 @@ class ModuleContext:
116
119
  shared: dict[str, Any]
117
120
  tool_cache: ToolCache
118
121
  request_metadata: RequestMetadata
122
+ agui_run: "AgUiRunState | None"
119
123
 
120
124
  def __init__( # All service strategies are mandatory constructor args # ruff: ignore[too-many-arguments, too-many-positional-arguments]
121
125
  self,
@@ -180,6 +184,7 @@ class ModuleContext:
180
184
  self.shared = shared if shared is not None else {}
181
185
  self.tool_cache = tool_cache or ToolCache()
182
186
  self.request_metadata = RequestMetadata(request_metadata)
187
+ self.agui_run = None
183
188
 
184
189
  async def get_module_schemas_by_id(
185
190
  self,
@@ -34,6 +34,12 @@ class StreamCancelledOutput(UtilityProtocol):
34
34
  reason: str = "cancelled"
35
35
 
36
36
 
37
+ class StreamHeartbeatOutput(UtilityProtocol):
38
+ """Liveness beat from a module that is running but silent; consumers ignore it."""
39
+
40
+ protocol: Literal["stream.heartbeat"] = "stream.heartbeat"
41
+
42
+
37
43
  class HealthcheckPingInput(UtilityProtocol):
38
44
  """Input for healthcheck ping request."""
39
45
 
@@ -32,6 +32,14 @@ class ModuleSettings(BaseSettings):
32
32
  file_history_flush_threshold: int = Field(
33
33
  default=10, description="Dirty-entry count that triggers a file-history flush."
34
34
  )
35
+ heartbeat_interval_s: float = Field(
36
+ default=30.0,
37
+ gt=0,
38
+ description=(
39
+ "Silence after which a running task writes a liveness frame (AG-UI STATE_DELTA or "
40
+ "stream.heartbeat). Keep it below the gateway read/dial-back idle timeouts."
41
+ ),
42
+ )
35
43
 
36
44
 
37
45
  @lru_cache(maxsize=1)