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.
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/PKG-INFO +1 -1
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/pyproject.toml +1 -1
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/__version__.py +1 -1
- digitalkin-1.0.0.dev2/src/digitalkin/core/task_manager/module_runner.py +170 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/grpc_servers/gateway_constants.py +2 -1
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/grpc_servers/gateway_servicer.py +40 -58
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/grpc_servers/module_server.py +11 -36
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/grpc_servers/module_servicer.py +41 -7
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/models/settings/gateway.py +11 -4
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/communication/gateway_consumer.py +7 -3
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/registry/grpc_registry.py +10 -12
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin.egg-info/PKG-INFO +1 -1
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin.egg-info/SOURCES.txt +1 -1
- digitalkin-1.0.0.dev0/src/digitalkin/core/task_manager/task_dispatcher.py +0 -316
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/LICENSE +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/README.md +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/base_server/__init__.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/base_server/mock/__init__.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/base_server/mock/mock_pb2.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/base_server/mock/mock_pb2_grpc.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/base_server/server_async_insecure.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/base_server/server_async_secure.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/base_server/server_sync_insecure.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/base_server/server_sync_secure.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/bench_module/__init__.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/bench_module/echo_module.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/bench_module/models/__init__.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/bench_module/models/input.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/bench_module/models/output.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/bench_module/models/secret.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/bench_module/models/setup.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/bench_module/server.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/bench_module/triggers/__init__.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/bench_module/triggers/message_trigger.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/modules/__init__.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/modules/archetype_with_tools_module.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/modules/cpu_intensive_module.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/modules/dynamic_setup_module.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/modules/minimal_llm_module.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/modules/text_transform_module.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/monitoring/digitalkin_observability/__init__.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/monitoring/digitalkin_observability/http_server.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/monitoring/digitalkin_observability/interceptors.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/monitoring/digitalkin_observability/metrics.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/monitoring/digitalkin_observability/prometheus.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/monitoring/tests/test_metrics.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/redis_demo/client.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/redis_demo/echo_module.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/redis_demo/models/__init__.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/redis_demo/models/input.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/redis_demo/models/output.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/redis_demo/models/secret.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/redis_demo/models/setup.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/redis_demo/server.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/redis_demo/triggers/__init__.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/redis_demo/triggers/message_trigger.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/services/filesystem_module.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/examples/services/storage_module.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/setup.cfg +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/__init__.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/community/__init__.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/community/agno/__init__.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/community/agno/agno_adapter.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/community/agno/agui_tools.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/community/agno/hitl.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/core/__init__.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/core/common/__init__.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/core/common/factories.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/core/job_manager/__init__.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/core/job_manager/base_job_manager.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/core/job_manager/single_job_manager.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/core/profiling/__init__.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/core/profiling/step_timer.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/core/profiling/task_profiler.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/core/resilience/__init__.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/core/resilience/bulkhead.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/core/resilience/graceful_shutdown.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/core/resilience/session_reaper.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/core/resilience/watchdog.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/core/task_manager/__init__.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/core/task_manager/base_task_manager.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/core/task_manager/local_task_manager.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/core/task_manager/redis/__init__.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/core/task_manager/redis/instrumented.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/core/task_manager/redis/proto_streams.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/core/task_manager/redis/redis_checkpoint.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/core/task_manager/redis/redis_client.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/core/task_manager/redis/redis_idempotency.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/core/task_manager/redis/redis_signal.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/core/task_manager/redis/redis_state.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/core/task_manager/redis/redis_streams.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/core/task_manager/redis/shadow.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/core/task_manager/remote_task_manager.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/core/task_manager/task_executor.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/core/task_manager/task_session.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/core/task_manager/task_wrapper.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/grpc_servers/__init__.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/grpc_servers/_base_server.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/grpc_servers/interceptors/__init__.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/grpc_servers/interceptors/circuit_breaker_interceptor.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/grpc_servers/stream_error_codes.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/grpc_servers/stream_registry.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/grpc_servers/stream_session.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/grpc_servers/utils/__init__.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/grpc_servers/utils/circuit_breaker.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/grpc_servers/utils/exceptions.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/grpc_servers/utils/grpc_client_wrapper.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/grpc_servers/utils/grpc_error_handler.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/grpc_servers/utils/utility_schema_extender.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/logger.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/mixins/__init__.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/mixins/agui_mixin.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/mixins/base_mixin.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/mixins/cost_mixin.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/mixins/file_history_mixin.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/mixins/filesystem_mixin.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/mixins/logger_mixin.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/mixins/storage_mixin.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/models/__init__.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/models/core/__init__.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/models/core/job_manager_models.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/models/core/task_monitor.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/models/events/__init__.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/models/events/agent_events.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/models/grpc_servers/__init__.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/models/grpc_servers/models.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/models/grpc_servers/types.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/models/module/__init__.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/models/module/ag_ui.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/models/module/base_types.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/models/module/module.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/models/module/module_context.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/models/module/module_types.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/models/module/request_metadata.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/models/module/select_schema.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/models/module/setup_types.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/models/module/tool_cache.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/models/module/tool_reference.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/models/module/utility.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/models/services/__init__.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/models/services/cost.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/models/services/registry.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/models/services/storage.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/models/settings/__init__.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/models/settings/consumer.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/models/settings/profiling.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/models/settings/redis.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/models/settings/server/__init__.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/models/settings/server/channel.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/models/settings/server/grpc.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/models/settings/server/server.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/models/settings/utils/__init__.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/models/settings/utils/channel.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/modules/__init__.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/modules/_base_module.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/modules/archetype_module.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/modules/tool_module.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/modules/trigger_handler.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/modules/triggers/__init__.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/modules/triggers/healthcheck_ping_trigger.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/modules/triggers/healthcheck_services_trigger.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/modules/triggers/healthcheck_status_trigger.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/py.typed +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/__init__.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/base_strategy.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/communication/__init__.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/communication/communication_strategy.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/communication/default_communication.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/communication/grpc_communication.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/cost/__init__.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/cost/cost_strategy.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/cost/default_cost.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/cost/grpc_cost.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/filesystem/__init__.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/filesystem/default_filesystem.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/filesystem/filesystem_strategy.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/filesystem/grpc_filesystem.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/identity/__init__.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/identity/default_identity.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/identity/identity_strategy.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/registry/__init__.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/registry/default_registry.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/registry/exceptions.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/registry/registry_models.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/registry/registry_strategy.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/services_config.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/services_models.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/setup/__init__.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/setup/default_setup.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/setup/grpc_setup.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/setup/setup_strategy.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/storage/__init__.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/storage/default_storage.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/storage/grpc_storage.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/storage/storage_strategy.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/task_manager/__init__.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/task_manager/default_task_manager.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/task_manager/redis_task_manager.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/task_manager/task_manager_strategy.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/user_profile/__init__.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/user_profile/default_user_profile.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/user_profile/grpc_user_profile.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/user_profile/user_profile_strategy.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/utils/__init__.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/utils/arg_parser.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/utils/conditional_schema.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/utils/development_mode_action.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/utils/dynamic_schema.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/utils/llm_ready_schema.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/utils/package_discover.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/utils/proto_utils.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/utils/schema_splitter.py +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin.egg-info/dependency_links.txt +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin.egg-info/requires.txt +0 -0
- {digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin.egg-info/top_level.txt +0 -0
|
@@ -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()
|
{digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/grpc_servers/gateway_constants.py
RENAMED
|
@@ -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
|
# ══════════════════════════════════════════════════════════════════
|
{digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/grpc_servers/gateway_servicer.py
RENAMED
|
@@ -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
|
|
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
|
|
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 —
|
|
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 ""
|
{digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/grpc_servers/module_server.py
RENAMED
|
@@ -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
|
|
97
|
+
"""Register the embedded GatewayServicer.
|
|
99
98
|
|
|
100
|
-
|
|
101
|
-
|
|
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
|
-
|
|
113
|
-
|
|
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
|
-
|
|
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()
|
{digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/grpc_servers/module_servicer.py
RENAMED
|
@@ -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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
"
|
|
43
|
-
"
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
#
|
|
39
|
-
# from
|
|
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 =
|
|
186
|
+
self._grpc_options = _GRPC_SERVER_OPTIONS
|
|
183
187
|
|
|
184
188
|
@classmethod
|
|
185
189
|
def standalone(cls, config: ConsumerConfig) -> GatewayConsumer:
|
{digitalkin-1.0.0.dev0 → digitalkin-1.0.0.dev2}/src/digitalkin/services/registry/grpc_registry.py
RENAMED
|
@@ -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
|
-
"""
|
|
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
|
|
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
|
|
76
|
-
"
|
|
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
|
|
81
|
-
return
|
|
82
|
-
return
|
|
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(
|