digitalkin 1.0.0.dev2__tar.gz → 1.0.0.dev3__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.dev2 → digitalkin-1.0.0.dev3}/PKG-INFO +1 -1
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/pyproject.toml +1 -1
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/__version__.py +1 -1
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/core/job_manager/base_job_manager.py +1 -88
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/core/job_manager/single_job_manager.py +56 -200
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/core/profiling/task_profiler.py +30 -0
- digitalkin-1.0.0.dev3/src/digitalkin/core/resilience/task_supervisor.py +37 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/core/task_manager/base_task_manager.py +0 -49
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/core/task_manager/local_task_manager.py +7 -2
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/core/task_manager/module_runner.py +43 -33
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/core/task_manager/redis/proto_streams.py +18 -2
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/core/task_manager/redis/redis_signal.py +3 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/core/task_manager/redis/redis_streams.py +2 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/core/task_manager/task_executor.py +34 -6
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/core/task_manager/task_session.py +29 -37
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/grpc_servers/gateway_constants.py +3 -5
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/grpc_servers/gateway_servicer.py +132 -50
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/grpc_servers/module_server.py +1 -7
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/grpc_servers/module_servicer.py +3 -375
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/grpc_servers/stream_error_codes.py +1 -2
- digitalkin-1.0.0.dev3/src/digitalkin/grpc_servers/stream_registry.py +247 -0
- digitalkin-1.0.0.dev3/src/digitalkin/grpc_servers/stream_session.py +54 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/grpc_servers/utils/grpc_client_wrapper.py +5 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/models/settings/gateway.py +6 -13
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/modules/_base_module.py +54 -15
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/services/communication/gateway_consumer.py +17 -2
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/utils/conditional_schema.py +1 -3
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin.egg-info/PKG-INFO +1 -1
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin.egg-info/SOURCES.txt +1 -0
- digitalkin-1.0.0.dev2/src/digitalkin/grpc_servers/stream_registry.py +0 -261
- digitalkin-1.0.0.dev2/src/digitalkin/grpc_servers/stream_session.py +0 -124
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/LICENSE +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/README.md +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/examples/base_server/__init__.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/examples/base_server/mock/__init__.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/examples/base_server/mock/mock_pb2.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/examples/base_server/mock/mock_pb2_grpc.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/examples/base_server/server_async_insecure.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/examples/base_server/server_async_secure.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/examples/base_server/server_sync_insecure.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/examples/base_server/server_sync_secure.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/examples/bench_module/__init__.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/examples/bench_module/echo_module.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/examples/bench_module/models/__init__.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/examples/bench_module/models/input.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/examples/bench_module/models/output.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/examples/bench_module/models/secret.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/examples/bench_module/models/setup.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/examples/bench_module/server.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/examples/bench_module/triggers/__init__.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/examples/bench_module/triggers/message_trigger.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/examples/modules/__init__.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/examples/modules/archetype_with_tools_module.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/examples/modules/cpu_intensive_module.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/examples/modules/dynamic_setup_module.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/examples/modules/minimal_llm_module.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/examples/modules/text_transform_module.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/examples/monitoring/digitalkin_observability/__init__.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/examples/monitoring/digitalkin_observability/http_server.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/examples/monitoring/digitalkin_observability/interceptors.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/examples/monitoring/digitalkin_observability/metrics.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/examples/monitoring/digitalkin_observability/prometheus.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/examples/monitoring/tests/test_metrics.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/examples/redis_demo/client.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/examples/redis_demo/echo_module.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/examples/redis_demo/models/__init__.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/examples/redis_demo/models/input.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/examples/redis_demo/models/output.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/examples/redis_demo/models/secret.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/examples/redis_demo/models/setup.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/examples/redis_demo/server.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/examples/redis_demo/triggers/__init__.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/examples/redis_demo/triggers/message_trigger.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/examples/services/filesystem_module.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/examples/services/storage_module.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/setup.cfg +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/__init__.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/community/__init__.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/community/agno/__init__.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/community/agno/agno_adapter.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/community/agno/agui_tools.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/community/agno/hitl.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/core/__init__.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/core/common/__init__.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/core/common/factories.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/core/job_manager/__init__.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/core/profiling/__init__.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/core/profiling/step_timer.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/core/resilience/__init__.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/core/resilience/bulkhead.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/core/resilience/graceful_shutdown.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/core/resilience/session_reaper.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/core/resilience/watchdog.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/core/task_manager/__init__.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/core/task_manager/redis/__init__.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/core/task_manager/redis/instrumented.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/core/task_manager/redis/redis_checkpoint.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/core/task_manager/redis/redis_client.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/core/task_manager/redis/redis_idempotency.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/core/task_manager/redis/redis_state.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/core/task_manager/redis/shadow.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/core/task_manager/remote_task_manager.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/core/task_manager/task_wrapper.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/grpc_servers/__init__.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/grpc_servers/_base_server.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/grpc_servers/interceptors/__init__.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/grpc_servers/interceptors/circuit_breaker_interceptor.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/grpc_servers/utils/__init__.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/grpc_servers/utils/circuit_breaker.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/grpc_servers/utils/exceptions.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/grpc_servers/utils/grpc_error_handler.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/grpc_servers/utils/utility_schema_extender.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/logger.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/mixins/__init__.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/mixins/agui_mixin.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/mixins/base_mixin.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/mixins/cost_mixin.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/mixins/file_history_mixin.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/mixins/filesystem_mixin.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/mixins/logger_mixin.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/mixins/storage_mixin.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/models/__init__.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/models/core/__init__.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/models/core/job_manager_models.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/models/core/task_monitor.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/models/events/__init__.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/models/events/agent_events.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/models/grpc_servers/__init__.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/models/grpc_servers/models.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/models/grpc_servers/types.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/models/module/__init__.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/models/module/ag_ui.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/models/module/base_types.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/models/module/module.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/models/module/module_context.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/models/module/module_types.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/models/module/request_metadata.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/models/module/select_schema.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/models/module/setup_types.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/models/module/tool_cache.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/models/module/tool_reference.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/models/module/utility.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/models/services/__init__.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/models/services/cost.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/models/services/registry.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/models/services/storage.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/models/settings/__init__.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/models/settings/consumer.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/models/settings/profiling.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/models/settings/redis.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/models/settings/server/__init__.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/models/settings/server/channel.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/models/settings/server/grpc.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/models/settings/server/server.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/models/settings/utils/__init__.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/models/settings/utils/channel.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/modules/__init__.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/modules/archetype_module.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/modules/tool_module.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/modules/trigger_handler.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/modules/triggers/__init__.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/modules/triggers/healthcheck_ping_trigger.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/modules/triggers/healthcheck_services_trigger.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/modules/triggers/healthcheck_status_trigger.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/py.typed +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/services/__init__.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/services/base_strategy.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/services/communication/__init__.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/services/communication/communication_strategy.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/services/communication/default_communication.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/services/communication/grpc_communication.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/services/cost/__init__.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/services/cost/cost_strategy.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/services/cost/default_cost.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/services/cost/grpc_cost.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/services/filesystem/__init__.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/services/filesystem/default_filesystem.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/services/filesystem/filesystem_strategy.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/services/filesystem/grpc_filesystem.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/services/identity/__init__.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/services/identity/default_identity.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/services/identity/identity_strategy.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/services/registry/__init__.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/services/registry/default_registry.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/services/registry/exceptions.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/services/registry/grpc_registry.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/services/registry/registry_models.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/services/registry/registry_strategy.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/services/services_config.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/services/services_models.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/services/setup/__init__.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/services/setup/default_setup.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/services/setup/grpc_setup.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/services/setup/setup_strategy.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/services/storage/__init__.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/services/storage/default_storage.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/services/storage/grpc_storage.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/services/storage/storage_strategy.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/services/task_manager/__init__.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/services/task_manager/default_task_manager.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/services/task_manager/redis_task_manager.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/services/task_manager/task_manager_strategy.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/services/user_profile/__init__.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/services/user_profile/default_user_profile.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/services/user_profile/grpc_user_profile.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/services/user_profile/user_profile_strategy.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/utils/__init__.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/utils/arg_parser.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/utils/development_mode_action.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/utils/dynamic_schema.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/utils/llm_ready_schema.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/utils/package_discover.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/utils/proto_utils.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/utils/schema_splitter.py +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin.egg-info/dependency_links.txt +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin.egg-info/requires.txt +0 -0
- {digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin.egg-info/top_level.txt +0 -0
{digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/core/job_manager/base_job_manager.py
RENAMED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
"""Background module manager."""
|
|
2
2
|
|
|
3
3
|
import abc
|
|
4
|
-
from collections.abc import
|
|
5
|
-
from contextlib import AbstractAsyncContextManager
|
|
4
|
+
from collections.abc import Callable, Coroutine
|
|
6
5
|
from typing import Any, Generic
|
|
7
6
|
|
|
8
7
|
from digitalkin.core.task_manager.base_task_manager import BaseTaskManager
|
|
@@ -79,18 +78,6 @@ class BaseJobManager(abc.ABC, Generic[InputModelT, OutputModelT, SetupModelT]):
|
|
|
79
78
|
"""
|
|
80
79
|
await self._task_manager.create_task(task_id, mission_id, module, coro, **kwargs)
|
|
81
80
|
|
|
82
|
-
async def clean_session(self, task_id: str, mission_id: str) -> bool:
|
|
83
|
-
"""Clean a task's session.
|
|
84
|
-
|
|
85
|
-
Args:
|
|
86
|
-
task_id: Unique identifier for the task.
|
|
87
|
-
mission_id: Mission identifier.
|
|
88
|
-
|
|
89
|
-
Returns:
|
|
90
|
-
bool: True if the task was successfully cancelled, False otherwise.
|
|
91
|
-
"""
|
|
92
|
-
return await self._task_manager.clean_session(task_id, mission_id)
|
|
93
|
-
|
|
94
81
|
async def cancel_task(self, task_id: str, mission_id: str, timeout: float | None = None) -> bool:
|
|
95
82
|
"""Cancel a task.
|
|
96
83
|
|
|
@@ -164,48 +151,6 @@ class BaseJobManager(abc.ABC, Generic[InputModelT, OutputModelT, SetupModelT]):
|
|
|
164
151
|
|
|
165
152
|
return callback_wrapper
|
|
166
153
|
|
|
167
|
-
@abc.abstractmethod
|
|
168
|
-
def generate_stream_consumer(
|
|
169
|
-
self, job_id: str
|
|
170
|
-
) -> AbstractAsyncContextManager[AsyncGenerator[dict[str, Any], None]]:
|
|
171
|
-
"""Generate a stream consumer for the job's message stream.
|
|
172
|
-
|
|
173
|
-
Args:
|
|
174
|
-
job_id: The unique identifier of the job to filter messages for.
|
|
175
|
-
|
|
176
|
-
Yields:
|
|
177
|
-
dict[str, Any]: The messages from the associated module's stream.
|
|
178
|
-
"""
|
|
179
|
-
|
|
180
|
-
@abc.abstractmethod
|
|
181
|
-
async def create_module_instance_job(
|
|
182
|
-
self,
|
|
183
|
-
input_data: InputModelT,
|
|
184
|
-
setup_data: SetupModelT,
|
|
185
|
-
mission_id: str,
|
|
186
|
-
setup_id: str,
|
|
187
|
-
setup_version_id: str,
|
|
188
|
-
request_metadata: dict[str, str] | None = None,
|
|
189
|
-
job_id: str | None = None,
|
|
190
|
-
tool_cache: Any = None,
|
|
191
|
-
) -> str:
|
|
192
|
-
"""Create and start a new job for the module's instance.
|
|
193
|
-
|
|
194
|
-
Args:
|
|
195
|
-
input_data: The input data required to start the job.
|
|
196
|
-
setup_data: The setup configuration for the module.
|
|
197
|
-
mission_id: The mission ID associated with the job.
|
|
198
|
-
setup_id: The setup ID.
|
|
199
|
-
setup_version_id: The setup version ID associated with the module.
|
|
200
|
-
request_metadata: gRPC request metadata (headers) to forward to the module.
|
|
201
|
-
job_id: Optional externally-provided job ID (e.g., Gateway's task_id).
|
|
202
|
-
If None, a UUID is minted internally.
|
|
203
|
-
tool_cache: Pre-resolved ToolCache to inject (skips per-request resolution).
|
|
204
|
-
|
|
205
|
-
Returns:
|
|
206
|
-
str: The unique identifier (job ID) of the created job.
|
|
207
|
-
"""
|
|
208
|
-
|
|
209
154
|
@abc.abstractmethod
|
|
210
155
|
async def generate_config_setup_module_response(self, job_id: str) -> SetupModelT | ModuleCodeModel:
|
|
211
156
|
"""Generate a stream consumer for a module's output data.
|
|
@@ -249,38 +194,6 @@ class BaseJobManager(abc.ABC, Generic[InputModelT, OutputModelT, SetupModelT]):
|
|
|
249
194
|
Exception: If the module fails to start.
|
|
250
195
|
"""
|
|
251
196
|
|
|
252
|
-
@abc.abstractmethod
|
|
253
|
-
async def stop_module(self, job_id: str) -> bool:
|
|
254
|
-
"""Stop a running module job.
|
|
255
|
-
|
|
256
|
-
Args:
|
|
257
|
-
job_id: The unique identifier of the job to stop.
|
|
258
|
-
|
|
259
|
-
Returns:
|
|
260
|
-
bool: True if the job was successfully stopped, False if it does not exist.
|
|
261
|
-
"""
|
|
262
|
-
|
|
263
|
-
@abc.abstractmethod
|
|
264
|
-
async def wait_for_completion(self, job_id: str) -> None:
|
|
265
|
-
"""Wait for a task to complete.
|
|
266
|
-
|
|
267
|
-
This method blocks until the specified job has reached a terminal state.
|
|
268
|
-
SingleJobManager awaits the asyncio.Task directly.
|
|
269
|
-
|
|
270
|
-
Args:
|
|
271
|
-
job_id: The unique identifier of the job to wait for.
|
|
272
|
-
|
|
273
|
-
Raises:
|
|
274
|
-
KeyError: If the job_id is not found.
|
|
275
|
-
"""
|
|
276
|
-
|
|
277
|
-
@abc.abstractmethod
|
|
278
|
-
async def stop_all_modules(self) -> None:
|
|
279
|
-
"""Stop all currently running module jobs.
|
|
280
|
-
|
|
281
|
-
This method ensures that all active jobs are gracefully terminated.
|
|
282
|
-
"""
|
|
283
|
-
|
|
284
197
|
@abc.abstractmethod
|
|
285
198
|
async def list_modules(self) -> dict[str, dict[str, Any]]:
|
|
286
199
|
"""List all modules along with their statuses.
|
|
@@ -11,7 +11,6 @@ from __future__ import annotations
|
|
|
11
11
|
import asyncio
|
|
12
12
|
import os
|
|
13
13
|
import uuid
|
|
14
|
-
from contextlib import asynccontextmanager
|
|
15
14
|
from typing import TYPE_CHECKING, Any
|
|
16
15
|
|
|
17
16
|
import grpc
|
|
@@ -26,7 +25,7 @@ from digitalkin.models.module.base_types import DataModel, InputModelT, OutputMo
|
|
|
26
25
|
from digitalkin.models.module.module import ModuleCodeModel
|
|
27
26
|
|
|
28
27
|
if TYPE_CHECKING:
|
|
29
|
-
from collections.abc import
|
|
28
|
+
from collections.abc import Callable
|
|
30
29
|
|
|
31
30
|
from digitalkin.core.task_manager.redis.redis_client import RedisClient
|
|
32
31
|
from digitalkin.core.task_manager.redis.redis_streams import RedisStreamWriter
|
|
@@ -239,104 +238,8 @@ class SingleJobManager(BaseJobManager[InputModelT, OutputModelT, SetupModelT]):
|
|
|
239
238
|
except asyncio.QueueFull:
|
|
240
239
|
logger.warning("Queue full, rejecting new message", extra={"job_id": job_id})
|
|
241
240
|
|
|
242
|
-
|
|
243
|
-
async def generate_stream_consumer(self, job_id: str) -> AsyncIterator[AsyncGenerator[dict[str, Any], None]]:
|
|
244
|
-
"""Generate a stream consumer for a module's output data.
|
|
245
|
-
|
|
246
|
-
This method creates an asynchronous generator that streams output data
|
|
247
|
-
from a specific module job. If the module does not exist, it generates
|
|
248
|
-
an error message.
|
|
249
|
-
|
|
250
|
-
Args:
|
|
251
|
-
job_id: The unique identifier of the job.
|
|
252
|
-
|
|
253
|
-
Yields:
|
|
254
|
-
AsyncGenerator: A stream of output data or error messages.
|
|
255
|
-
"""
|
|
256
|
-
if (session := self.tasks_sessions.get(job_id, None)) is None:
|
|
257
|
-
|
|
258
|
-
async def _error_gen() -> AsyncGenerator[ # noqa: RUF029
|
|
259
|
-
dict[str, Any], None
|
|
260
|
-
]: # Async generator type required by caller even though body uses yield
|
|
261
|
-
"""Generate an error message for a non-existent module.
|
|
262
|
-
|
|
263
|
-
Yields:
|
|
264
|
-
AsyncGenerator: A generator yielding an error message.
|
|
265
|
-
"""
|
|
266
|
-
yield {
|
|
267
|
-
"error": {
|
|
268
|
-
"error_message": f"Module {job_id} not found",
|
|
269
|
-
"code": grpc.StatusCode.NOT_FOUND,
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
yield _error_gen()
|
|
274
|
-
return
|
|
275
|
-
|
|
276
|
-
logger.debug("Session: %s with Module %s", job_id, session.module)
|
|
277
|
-
|
|
278
|
-
async def _stream() -> AsyncGenerator[dict[str, Any], Any]:
|
|
279
|
-
"""Stream output data from the module with bounded blocking.
|
|
280
|
-
|
|
281
|
-
Uses a 1-second timeout on queue.get() to periodically re-check
|
|
282
|
-
termination flags, preventing indefinite hangs when the task crashes
|
|
283
|
-
without producing output.
|
|
284
|
-
|
|
285
|
-
Termination behavior:
|
|
286
|
-
- cancelled: abort immediately (abnormal, discard remaining)
|
|
287
|
-
- stream_closed / completed / failed: drain remaining queue items, then exit
|
|
288
|
-
|
|
289
|
-
Yields:
|
|
290
|
-
dict: Output data generated by the module.
|
|
291
|
-
"""
|
|
292
|
-
while True:
|
|
293
|
-
if session.cancelled:
|
|
294
|
-
logger.debug("Stream cancelled for job %s", job_id)
|
|
295
|
-
break
|
|
296
|
-
|
|
297
|
-
# If no more output will be produced, drain remaining items and exit
|
|
298
|
-
if session.stream_closed or session.status in {"completed", "failed"}:
|
|
299
|
-
while not session.queue.empty():
|
|
300
|
-
msg = session.queue.get_nowait()
|
|
301
|
-
try:
|
|
302
|
-
yield msg
|
|
303
|
-
finally:
|
|
304
|
-
session.queue.task_done()
|
|
305
|
-
logger.debug(
|
|
306
|
-
"Stream drained for job %s: status=%s, stream_closed=%s",
|
|
307
|
-
job_id,
|
|
308
|
-
session.status,
|
|
309
|
-
session.stream_closed,
|
|
310
|
-
)
|
|
311
|
-
break
|
|
312
|
-
|
|
313
|
-
try:
|
|
314
|
-
msg = await asyncio.wait_for(session.queue.get(), timeout=0.25)
|
|
315
|
-
except asyncio.TimeoutError:
|
|
316
|
-
continue
|
|
317
|
-
|
|
318
|
-
try:
|
|
319
|
-
yield msg
|
|
320
|
-
finally:
|
|
321
|
-
session.queue.task_done()
|
|
322
|
-
|
|
323
|
-
if session.cancelled:
|
|
324
|
-
break
|
|
325
|
-
|
|
326
|
-
try:
|
|
327
|
-
yield _stream()
|
|
328
|
-
finally:
|
|
329
|
-
# Write EOS to Redis Stream if writer exists (cleanup on stream close)
|
|
330
|
-
writer = self._stream_writers.pop(job_id, None) if self._stream_writers is not None else None
|
|
331
|
-
if writer is not None:
|
|
332
|
-
try:
|
|
333
|
-
await writer.write_eos()
|
|
334
|
-
except Exception:
|
|
335
|
-
logger.warning("Redis stream EOS write failed", extra={"job_id": job_id})
|
|
336
|
-
|
|
337
|
-
async def create_module_instance_job(
|
|
241
|
+
async def preload_instance(
|
|
338
242
|
self,
|
|
339
|
-
input_data: InputModelT,
|
|
340
243
|
setup_data: SetupModelT,
|
|
341
244
|
mission_id: str,
|
|
342
245
|
setup_id: str,
|
|
@@ -345,28 +248,32 @@ class SingleJobManager(BaseJobManager[InputModelT, OutputModelT, SetupModelT]):
|
|
|
345
248
|
job_id: str | None = None,
|
|
346
249
|
tool_cache: Any = None,
|
|
347
250
|
callback: Callable | None = None,
|
|
348
|
-
) -> str:
|
|
349
|
-
"""
|
|
251
|
+
) -> tuple[Any, str, Callable]:
|
|
252
|
+
"""Phase 3.A: Build + warm a module instance without input.
|
|
253
|
+
|
|
254
|
+
Calls the factory, wires the redis task manager + callback, and
|
|
255
|
+
runs the module's ``prepare()`` (which is idempotent — the later
|
|
256
|
+
``start()`` call short-circuits past it).
|
|
257
|
+
|
|
258
|
+
Designed so the dial-back orchestrator can pay LiteLLM/agno init
|
|
259
|
+
costs (~440 ms) in parallel with the consumer's first reply RTT.
|
|
350
260
|
|
|
351
261
|
Args:
|
|
352
|
-
input_data: The input data required to start the job.
|
|
353
262
|
setup_data: The setup configuration for the module.
|
|
354
|
-
mission_id:
|
|
355
|
-
setup_id:
|
|
356
|
-
setup_version_id:
|
|
357
|
-
request_metadata: gRPC request metadata (headers)
|
|
358
|
-
job_id: Optional externally-provided job ID
|
|
359
|
-
tool_cache: Pre-resolved ToolCache
|
|
360
|
-
callback: Direct output callback
|
|
361
|
-
|
|
263
|
+
mission_id: Mission ID.
|
|
264
|
+
setup_id: Setup ID.
|
|
265
|
+
setup_version_id: Setup version ID.
|
|
266
|
+
request_metadata: gRPC request metadata (headers).
|
|
267
|
+
job_id: Optional externally-provided job ID.
|
|
268
|
+
tool_cache: Pre-resolved ToolCache.
|
|
269
|
+
callback: Direct output callback. If None, the in-memory
|
|
270
|
+
queue path is wired for the eventual ``run_preloaded``.
|
|
362
271
|
|
|
363
272
|
Returns:
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
Raises:
|
|
367
|
-
Exception: If the module fails to start.
|
|
273
|
+
``(module, job_id, callback)`` — pass to ``run_preloaded``.
|
|
368
274
|
"""
|
|
369
275
|
from digitalkin.core.profiling.step_timer import StepTimer
|
|
276
|
+
from digitalkin.services.task_manager.redis_task_manager import RedisTaskManager
|
|
370
277
|
|
|
371
278
|
timer = StepTimer()
|
|
372
279
|
job_id = job_id or str(uuid.uuid4())
|
|
@@ -381,9 +288,6 @@ class SingleJobManager(BaseJobManager[InputModelT, OutputModelT, SetupModelT]):
|
|
|
381
288
|
)
|
|
382
289
|
timer.mark("factory_create")
|
|
383
290
|
|
|
384
|
-
# Redis-backed signal service for cross-process signal delivery
|
|
385
|
-
from digitalkin.services.task_manager.redis_task_manager import RedisTaskManager
|
|
386
|
-
|
|
387
291
|
module.context.task_manager = RedisTaskManager(self._redis_client)
|
|
388
292
|
timer.mark("redis_task_manager")
|
|
389
293
|
|
|
@@ -394,97 +298,49 @@ class SingleJobManager(BaseJobManager[InputModelT, OutputModelT, SetupModelT]):
|
|
|
394
298
|
callback = await self.job_specific_callback(self.add_to_queue, job_id)
|
|
395
299
|
timer.mark("default_callback")
|
|
396
300
|
|
|
397
|
-
await
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
module.start(input_data, setup_data, callback, done_callback=None), # type: ignore[arg-type]
|
|
402
|
-
)
|
|
403
|
-
timer.mark("create_task")
|
|
404
|
-
timer.log("create_module_instance_job", task_id=job_id)
|
|
405
|
-
logger.info("Managed task started: '%s'", job_id, extra={"task_id": job_id})
|
|
406
|
-
return job_id
|
|
407
|
-
|
|
408
|
-
async def clean_session(self, task_id: str, mission_id: str) -> bool:
|
|
409
|
-
"""Clean a task's session.
|
|
301
|
+
await module.prepare(setup_data, callback)
|
|
302
|
+
timer.mark("prepare")
|
|
303
|
+
timer.log("preload_instance", task_id=job_id)
|
|
304
|
+
return module, job_id, callback
|
|
410
305
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
306
|
+
async def run_preloaded(
|
|
307
|
+
self,
|
|
308
|
+
module: Any,
|
|
309
|
+
job_id: str,
|
|
310
|
+
mission_id: str,
|
|
311
|
+
input_data: InputModelT,
|
|
312
|
+
setup_data: SetupModelT,
|
|
313
|
+
callback: Callable,
|
|
314
|
+
) -> str:
|
|
315
|
+
"""Phase 3.A: Run a pre-prepared module instance with input.
|
|
419
316
|
|
|
420
|
-
|
|
421
|
-
|
|
317
|
+
``module`` must come from :meth:`preload_instance`. Schedules
|
|
318
|
+
the run in the task manager and returns the job_id.
|
|
422
319
|
|
|
423
320
|
Args:
|
|
424
|
-
|
|
321
|
+
module: Pre-prepared module instance.
|
|
322
|
+
job_id: Job/task ID assigned by ``preload_instance``.
|
|
323
|
+
mission_id: Mission ID for task manager scoping.
|
|
324
|
+
input_data: The first input (the query) to feed ``run()``.
|
|
325
|
+
setup_data: The setup the instance was prepared with.
|
|
326
|
+
callback: Output callback (already attached to context).
|
|
425
327
|
|
|
426
328
|
Returns:
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
Raises:
|
|
430
|
-
Exception: If an error occurs while stopping the module.
|
|
329
|
+
The ``job_id`` (echoed for caller convenience).
|
|
431
330
|
"""
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
logger.debug("debug:stop_module acquiring lock job_id=%s", job_id)
|
|
435
|
-
async with self._lock:
|
|
436
|
-
session = self.tasks_sessions.get(job_id)
|
|
437
|
-
|
|
438
|
-
if not session:
|
|
439
|
-
logger.warning("Session not found", extra={"job_id": job_id})
|
|
440
|
-
return False
|
|
441
|
-
try:
|
|
442
|
-
await session.module.stop()
|
|
443
|
-
await self.cancel_task(job_id, session.mission_id)
|
|
444
|
-
logger.debug(
|
|
445
|
-
"Module stopped successfully",
|
|
446
|
-
extra={"job_id": job_id, "mission_id": session.mission_id},
|
|
447
|
-
)
|
|
448
|
-
except Exception:
|
|
449
|
-
logger.exception("Error stopping module", extra={"job_id": job_id})
|
|
450
|
-
raise
|
|
451
|
-
else:
|
|
452
|
-
return True
|
|
453
|
-
finally:
|
|
454
|
-
# Clean up stream writer if consumer never started
|
|
455
|
-
if self._stream_writers is not None:
|
|
456
|
-
writer = self._stream_writers.pop(job_id, None)
|
|
457
|
-
if writer is not None:
|
|
458
|
-
try:
|
|
459
|
-
await writer.write_eos()
|
|
460
|
-
except Exception:
|
|
461
|
-
logger.debug("EOS write failed during stop", extra={"job_id": job_id})
|
|
462
|
-
|
|
463
|
-
async def wait_for_completion(self, job_id: str) -> None:
|
|
464
|
-
"""Wait for a task to complete by awaiting its asyncio.Task.
|
|
465
|
-
|
|
466
|
-
Idempotent — safe to call after the task has already been cleaned up
|
|
467
|
-
(e.g. by deferred cleanup during signal cancellation).
|
|
331
|
+
from digitalkin.core.profiling.step_timer import StepTimer
|
|
468
332
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
# Snapshot job IDs while holding lock
|
|
481
|
-
async with self._lock:
|
|
482
|
-
job_ids = list(self.tasks_sessions.keys())
|
|
483
|
-
|
|
484
|
-
# Release lock before calling stop_module (which has its own lock)
|
|
485
|
-
if job_ids:
|
|
486
|
-
stop_tasks = [self.stop_module(job_id) for job_id in job_ids]
|
|
487
|
-
await asyncio.gather(*stop_tasks, return_exceptions=True)
|
|
333
|
+
timer = StepTimer()
|
|
334
|
+
await self.create_task(
|
|
335
|
+
job_id,
|
|
336
|
+
mission_id,
|
|
337
|
+
module,
|
|
338
|
+
module.start(input_data, setup_data, callback, done_callback=None),
|
|
339
|
+
)
|
|
340
|
+
timer.mark("create_task")
|
|
341
|
+
timer.log("run_preloaded", task_id=job_id)
|
|
342
|
+
logger.info("Managed task started: '%s'", job_id, extra={"task_id": job_id})
|
|
343
|
+
return job_id
|
|
488
344
|
|
|
489
345
|
async def list_modules(self) -> dict[str, dict[str, Any]]:
|
|
490
346
|
"""List all modules along with their statuses.
|
{digitalkin-1.0.0.dev2 → digitalkin-1.0.0.dev3}/src/digitalkin/core/profiling/task_profiler.py
RENAMED
|
@@ -10,6 +10,35 @@ from typing import Any
|
|
|
10
10
|
|
|
11
11
|
from digitalkin.logger import logger
|
|
12
12
|
|
|
13
|
+
# Phase 7.C: rotate per-task profile files to avoid unbounded growth.
|
|
14
|
+
# Default keeps the most recent N profiles by mtime; configure via
|
|
15
|
+
# ``DIGITALKIN_PROFILER_KEEP_N``.
|
|
16
|
+
PROFILER_KEEP_N = int(os.environ.get("DIGITALKIN_PROFILER_KEEP_N", "100"))
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _rotate_profiles(output_dir: str, keep_n: int, suffixes: tuple[str, ...]) -> None:
|
|
20
|
+
"""Trim ``output_dir`` to the most recent ``keep_n`` files by mtime.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
output_dir: Directory containing profile files.
|
|
24
|
+
keep_n: Number of files to keep. ``<= 0`` disables rotation.
|
|
25
|
+
suffixes: File extensions to include in rotation (e.g. ``(".html",)``).
|
|
26
|
+
"""
|
|
27
|
+
if keep_n <= 0:
|
|
28
|
+
return
|
|
29
|
+
try:
|
|
30
|
+
candidates = [p for p in Path(output_dir).iterdir() if p.is_file() and p.suffix in suffixes]
|
|
31
|
+
except OSError:
|
|
32
|
+
return
|
|
33
|
+
if len(candidates) <= keep_n:
|
|
34
|
+
return
|
|
35
|
+
candidates.sort(key=lambda p: p.stat().st_mtime, reverse=True)
|
|
36
|
+
for stale in candidates[keep_n:]:
|
|
37
|
+
try:
|
|
38
|
+
stale.unlink()
|
|
39
|
+
except OSError:
|
|
40
|
+
logger.debug("Profiler rotation: could not delete %s", stale)
|
|
41
|
+
|
|
13
42
|
|
|
14
43
|
class ProfilerMode(str, Enum):
|
|
15
44
|
"""Profiler backend selection."""
|
|
@@ -121,6 +150,7 @@ class TaskProfiler:
|
|
|
121
150
|
Path(path).write_text(self._profiler.output_html(), encoding="utf-8")
|
|
122
151
|
logger.info("Pyinstrument profile saved: %s", path)
|
|
123
152
|
logger.info("Pyinstrument summary:\n%s", self._profiler.output_text())
|
|
153
|
+
_rotate_profiles(self._output_dir, PROFILER_KEEP_N, (".html",))
|
|
124
154
|
|
|
125
155
|
except Exception:
|
|
126
156
|
logger.exception("Failed to stop/save profiler %s for task %s", self._mode.value, self._task_id)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""Tiny helper: log unhandled exceptions on fire-and-forget asyncio tasks."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from digitalkin.logger import logger
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def log_unhandled(task: asyncio.Task[Any]) -> None:
|
|
12
|
+
"""Done-callback that logs uncaught exceptions on a fire-and-forget task.
|
|
13
|
+
|
|
14
|
+
Cancellation and clean exits are silent. Anything else is logged at
|
|
15
|
+
error level with the task name and traceback — this replaces asyncio's
|
|
16
|
+
opaque ``Task exception was never retrieved`` warning with an
|
|
17
|
+
actionable log line.
|
|
18
|
+
|
|
19
|
+
Usage:
|
|
20
|
+
|
|
21
|
+
task = asyncio.create_task(coro, name="my_daemon")
|
|
22
|
+
task.add_done_callback(log_unhandled)
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
task: The done asyncio task to inspect.
|
|
26
|
+
"""
|
|
27
|
+
if task.cancelled():
|
|
28
|
+
return
|
|
29
|
+
exc = task.exception()
|
|
30
|
+
if exc is not None:
|
|
31
|
+
logger.error(
|
|
32
|
+
"Background task '%s' failed with %s: %s",
|
|
33
|
+
task.get_name(),
|
|
34
|
+
type(exc).__name__,
|
|
35
|
+
exc,
|
|
36
|
+
exc_info=exc,
|
|
37
|
+
)
|
|
@@ -46,7 +46,6 @@ class BaseTaskManager(ABC):
|
|
|
46
46
|
self._active_slots = 0
|
|
47
47
|
self._task_wait_timeout = float(os.environ.get("DIGITALKIN_TASK_WAIT_TIMEOUT", "30"))
|
|
48
48
|
self._stream_drain_timeout = float(os.environ.get("DIGITALKIN_STREAM_DRAIN_TIMEOUT", "2.0"))
|
|
49
|
-
self._cleanup_tasks: set[asyncio.Task] = set()
|
|
50
49
|
|
|
51
50
|
# Admission queue: allows tasks to wait for a slot instead of being rejected.
|
|
52
51
|
# Total in-system capacity = max_concurrent + max_queued.
|
|
@@ -278,49 +277,6 @@ class BaseTaskManager(ABC):
|
|
|
278
277
|
self.tasks_sessions[task_id] = session
|
|
279
278
|
return session
|
|
280
279
|
|
|
281
|
-
def _register_auto_cleanup(self, task_id: str, mission_id: str) -> None:
|
|
282
|
-
"""Register a done callback on the supervisor task for deferred cleanup.
|
|
283
|
-
|
|
284
|
-
When the supervisor finishes, waits for the stream consumer to drain
|
|
285
|
-
(up to 60s), then runs idempotent cleanup. Safe if the servicer
|
|
286
|
-
already cleaned up.
|
|
287
|
-
|
|
288
|
-
Args:
|
|
289
|
-
task_id: The ID of the task.
|
|
290
|
-
mission_id: The ID of the mission.
|
|
291
|
-
"""
|
|
292
|
-
supervisor = self.tasks.get(task_id)
|
|
293
|
-
if supervisor is None:
|
|
294
|
-
return
|
|
295
|
-
|
|
296
|
-
def _on_done(_: asyncio.Task) -> None:
|
|
297
|
-
t = asyncio.ensure_future(self._deferred_cleanup(task_id, mission_id))
|
|
298
|
-
self._cleanup_tasks.add(t)
|
|
299
|
-
t.add_done_callback(self._cleanup_tasks.discard)
|
|
300
|
-
|
|
301
|
-
supervisor.add_done_callback(_on_done)
|
|
302
|
-
|
|
303
|
-
async def _deferred_cleanup(self, task_id: str, mission_id: str) -> None:
|
|
304
|
-
"""Wait for stream drain then cleanup.
|
|
305
|
-
|
|
306
|
-
Args:
|
|
307
|
-
task_id: The ID of the task.
|
|
308
|
-
mission_id: The ID of the mission.
|
|
309
|
-
"""
|
|
310
|
-
session = self.tasks_sessions.get(task_id)
|
|
311
|
-
if session is None:
|
|
312
|
-
return
|
|
313
|
-
|
|
314
|
-
try:
|
|
315
|
-
await asyncio.wait_for(session._stream_closed.wait(), timeout=self._stream_drain_timeout) # noqa: SLF001
|
|
316
|
-
except asyncio.TimeoutError:
|
|
317
|
-
logger.warning(
|
|
318
|
-
"Stream drain timeout, proceeding with cleanup",
|
|
319
|
-
extra={"task_id": task_id, "mission_id": mission_id},
|
|
320
|
-
)
|
|
321
|
-
|
|
322
|
-
await self._cleanup_task(task_id, mission_id)
|
|
323
|
-
|
|
324
280
|
@abstractmethod
|
|
325
281
|
async def create_task(
|
|
326
282
|
self,
|
|
@@ -599,11 +555,6 @@ class BaseTaskManager(ABC):
|
|
|
599
555
|
cleanup_coros = [self._cleanup_task(task_id, mission_id) for task_id in remaining_sessions]
|
|
600
556
|
await asyncio.gather(*cleanup_coros, return_exceptions=True)
|
|
601
557
|
|
|
602
|
-
# Await any deferred cleanup tasks
|
|
603
|
-
if self._cleanup_tasks:
|
|
604
|
-
await asyncio.gather(*self._cleanup_tasks, return_exceptions=True)
|
|
605
|
-
self._cleanup_tasks.clear()
|
|
606
|
-
|
|
607
558
|
logger.info(
|
|
608
559
|
"TaskManager shutdown completed, cancelled: %d, failed: %d",
|
|
609
560
|
len(results) - len(failed_tasks),
|
|
@@ -62,15 +62,20 @@ class LocalTaskManager(BaseTaskManager):
|
|
|
62
62
|
},
|
|
63
63
|
)
|
|
64
64
|
|
|
65
|
-
# Execute task using TaskExecutor
|
|
65
|
+
# Execute task using TaskExecutor; cleanup runs inside the
|
|
66
|
+
# supervisor's `finally` (folded from former _deferred_cleanup).
|
|
67
|
+
async def _finalize() -> None:
|
|
68
|
+
await self._cleanup_task(task_id, mission_id=mission_id)
|
|
69
|
+
|
|
66
70
|
supervisor_task = await self._executor.execute_task(
|
|
67
71
|
task_id,
|
|
68
72
|
mission_id,
|
|
69
73
|
coro,
|
|
70
74
|
session,
|
|
75
|
+
on_finalize=_finalize,
|
|
76
|
+
stream_drain_timeout=self._stream_drain_timeout,
|
|
71
77
|
)
|
|
72
78
|
self.tasks[task_id] = supervisor_task
|
|
73
|
-
self._register_auto_cleanup(task_id, mission_id)
|
|
74
79
|
|
|
75
80
|
logger.info(
|
|
76
81
|
"Local task created and started: '%s'",
|