Runtime
This section covers the runtime execution engines available in the Kailash SDK.
Overview
The Kailash SDK provides multiple runtime engines for executing workflows:
LocalRuntime: Synchronous execution on the local machine
AsyncLocalRuntime: Asynchronous execution for I/O-bound operations
ParallelRuntime: Parallel execution using multiprocessing
DockerRuntime: Isolated execution in Docker containers
TestingRuntime: Mock runtime for testing
Runtime Selection
Runtimes are automatically selected based on the workflow configuration:
from kailash import Workflow
# Default: LocalRuntime
workflow = Workflow("my_workflow")
# Async runtime for I/O operations
workflow = Workflow("async_workflow", runtime="async")
# Parallel runtime for CPU-intensive tasks
workflow = Workflow("parallel_workflow", runtime="parallel")
# Docker runtime for isolation
workflow = Workflow("docker_workflow", runtime="docker")
LocalRuntime
The default runtime for synchronous execution.
- class kailash.runtime.local.LocalRuntime(debug: bool = False, enable_cycles: bool = True, enable_async: bool = True, max_concurrency: int = 10, user_context: Any | None = None, enable_monitoring: bool = True, enable_security: bool = False, enable_audit: bool = False, resource_limits: dict[str, Any] | None = None, secret_provider: Any | None = None, connection_validation: str = 'warn', conditional_execution: str = 'route_data', content_aware_success_detection: bool = True, persistent_mode: bool = False, enable_connection_sharing: bool = True, max_concurrent_workflows: int = 10, connection_pool_size: int = 20, enable_enterprise_monitoring: bool = False, enable_health_monitoring: bool = False, enable_resource_coordination: bool = True, circuit_breaker_config: dict | None = None, retry_policy_config: dict | None = None, connection_pool_config: dict | None = None, trust_context: Any | None = None, trust_verifier: Any | None = None, trust_verification_mode: str = 'disabled', audit_generator: Any | None = None, audit_log_to_stdout: bool = False, enable_resource_limits: bool = False, checkpoint_store: Any | None = None, checkpoint_after_each_node: bool = False, history_store: Any | None = None, sync_bridge_timeout: float | None = None)[source]
Bases:
BaseRuntime,CycleExecutionMixin,ValidationMixin,ConditionalExecutionMixinUnified runtime with enterprise capabilities.
This class provides a comprehensive, production-ready execution engine that seamlessly handles both traditional workflows and advanced cyclic patterns, with full enterprise feature integration through composable nodes.
- Inherits from:
BaseRuntime: Provides core runtime foundation and configuration CycleExecutionMixin: Provides shared cycle execution delegation ValidationMixin: Provides workflow validation and contract checking ConditionalExecutionMixin: Provides conditional execution and branching logic
Enterprise Features (Composably Integrated): - Access control via existing AccessControlManager and security nodes - Real-time monitoring via TaskManager and MetricsCollector - Audit logging via AuditLogNode and SecurityEventNode - Resource management via enterprise monitoring nodes - Async execution support for AsyncNode instances - Performance optimization via PerformanceBenchmarkNode
- Parameters:
debug (bool)
enable_cycles (bool)
enable_async (bool)
max_concurrency (int)
user_context (Any | None)
enable_monitoring (bool)
enable_security (bool)
enable_audit (bool)
secret_provider (Any | None)
connection_validation (str)
conditional_execution (str)
content_aware_success_detection (bool)
persistent_mode (bool)
enable_connection_sharing (bool)
max_concurrent_workflows (int)
connection_pool_size (int)
enable_enterprise_monitoring (bool)
enable_health_monitoring (bool)
enable_resource_coordination (bool)
circuit_breaker_config (dict | None)
retry_policy_config (dict | None)
connection_pool_config (dict | None)
trust_context (Any | None)
trust_verifier (Any | None)
trust_verification_mode (str)
audit_generator (Any | None)
audit_log_to_stdout (bool)
enable_resource_limits (bool)
checkpoint_store (Any | None)
checkpoint_after_each_node (bool)
history_store (Any | None)
sync_bridge_timeout (float | None)
- __init__(debug: bool = False, enable_cycles: bool = True, enable_async: bool = True, max_concurrency: int = 10, user_context: Any | None = None, enable_monitoring: bool = True, enable_security: bool = False, enable_audit: bool = False, resource_limits: dict[str, Any] | None = None, secret_provider: Any | None = None, connection_validation: str = 'warn', conditional_execution: str = 'route_data', content_aware_success_detection: bool = True, persistent_mode: bool = False, enable_connection_sharing: bool = True, max_concurrent_workflows: int = 10, connection_pool_size: int = 20, enable_enterprise_monitoring: bool = False, enable_health_monitoring: bool = False, enable_resource_coordination: bool = True, circuit_breaker_config: dict | None = None, retry_policy_config: dict | None = None, connection_pool_config: dict | None = None, trust_context: Any | None = None, trust_verifier: Any | None = None, trust_verification_mode: str = 'disabled', audit_generator: Any | None = None, audit_log_to_stdout: bool = False, enable_resource_limits: bool = False, checkpoint_store: Any | None = None, checkpoint_after_each_node: bool = False, history_store: Any | None = None, sync_bridge_timeout: float | None = None)[source]
Initialize the unified runtime.
- Parameters:
debug (bool) – Whether to enable debug logging.
enable_cycles (bool) – Whether to enable cyclic workflow support.
enable_async (bool) – Whether to enable async execution for async nodes.
max_concurrency (int) – Maximum concurrent async operations.
user_context (Any | None) – User context for access control (optional).
enable_monitoring (bool) – Whether to enable performance monitoring.
enable_security (bool) – Whether to enable security features.
enable_audit (bool) – Whether to enable audit logging.
resource_limits (dict[str, Any] | None) – Resource limits (memory_mb, cpu_cores, etc.).
secret_provider (Any | None) – Optional secret provider for runtime secret injection.
connection_validation (str) – Connection parameter validation mode: - “off”: No validation (backward compatibility) - “warn”: Log warnings on validation errors (default) - “strict”: Raise errors on validation failures
conditional_execution (str) – Execution strategy for conditional routing: - “route_data”: Current behavior - all nodes execute, data routing only (default) - “skip_branches”: New behavior - skip unreachable branches entirely
content_aware_success_detection (bool) – Whether to enable content-aware success detection: - True: Check return value content for success/failure patterns (default) - False: Only use exception-based failure detection (legacy mode)
persistent_mode (bool) – Whether to enable persistent runtime mode for long-running applications.
enable_connection_sharing (bool) – Whether to enable connection pool sharing across runtime instances.
max_concurrent_workflows (int) – Maximum number of concurrent workflows in persistent mode.
connection_pool_size (int) – Default size for connection pools.
sync_bridge_timeout (float | None) – Optional hard bound, in seconds, on the sync->async bridge join (issue #2081).
execute()called from inside a running event loop runs the workflow on a worker thread and waits for it. DefaultNonewaits indefinitely — a workflow may legitimately run for hours, and truncating one would trade a visible hang for silent data loss — but the wait is sliced, so a bridge that has stopped progressing logs a WARNING with its stack everySYNC_BRIDGE_WATCHDOG_INTERVALseconds instead of hanging mutely. Set a value to convert that into aRuntimeExecutionErrornaming the workflow.enable_enterprise_monitoring (bool)
enable_health_monitoring (bool)
enable_resource_coordination (bool)
circuit_breaker_config (dict | None)
retry_policy_config (dict | None)
connection_pool_config (dict | None)
trust_context (Any | None)
trust_verifier (Any | None)
trust_verification_mode (str)
audit_generator (Any | None)
audit_log_to_stdout (bool)
enable_resource_limits (bool)
checkpoint_store (Any | None)
checkpoint_after_each_node (bool)
history_store (Any | None)
- Raises:
ValueError – If
sync_bridge_timeoutis set and not positive.
- cyclic_executor
- on_node_complete(callback: Callable[[NodeCompletionEvent], None | Awaitable[None]]) Any[source]
Register callback for every node completion this runtime emits.
Returns an unregister function — call it to remove the callback.
Subscribers see one
NodeCompletionEventper node, regardless of whether the node succeeded or failed; the event’serrorfield carries the failure repr when set. The runtime appliesredact_event_for_persistence()to the event BEFORE dispatch so no subscriber ever observes a classified PK or a redacted field’s raw value.Subscriber exceptions are caught and logged at WARN level — a misbehaving subscriber MUST NOT take down workflow execution.
- Parameters:
callback (Callable[[NodeCompletionEvent], None | Awaitable[None]])
- Return type:
- execute(workflow: Workflow, task_manager: TaskManager | None = None, parameters: dict[str, dict[str, Any]] | dict[str, Any] | None = None, cancellation_token: CancellationToken | None = None, search_attributes: Dict[str, Any] | None = None, *, idempotency_key: str | None = None, force_resume_with_drift: bool = False, soft_time_limit: float | None = None, time_limit: float | None = None, **kwargs: Any) tuple[dict[str, Any], str | None][source]
Execute a workflow synchronously.
This method uses a persistent event loop across multiple executions, ensuring connection pools and async resources remain valid. This is critical for AsyncSQLDatabaseNode and other async components.
- Persistent Event Loop Benefits:
Connection pools remain valid across executions (no “Event loop closed” errors)
Better performance (no loop recreation overhead)
Efficient resource usage (connection pool reuse)
- Parameters:
workflow (Workflow) – Workflow to execute.
task_manager (TaskManager | None) – Optional task manager for tracking.
parameters (dict[str, dict[str, Any]] | dict[str, Any] | None) – Optional parameter overrides per node.
soft_time_limit (float | None) – Optional advisory deadline in seconds (#912). When reached, the running workflow is signalled via the cancellation token; user code MAY catch
SoftTimeLimitExceeded, finish in-flight work, and exit cleanly before the hard limit fires.time_limit (float | None) – Optional unconditional kill deadline in seconds (#912). When
time_limit + graceelapses, the wrapper raisesHardTimeLimitExceededregardless of soft-limit acknowledgement.cancellation_token (CancellationToken | None)
idempotency_key (str | None)
force_resume_with_drift (bool)
kwargs (Any)
- Returns:
Tuple of (results dict, run_id).
- Raises:
RuntimeExecutionError – If execution fails.
WorkflowValidationError – If workflow is invalid.
PermissionError – If access control denies execution.
SoftTimeLimitExceeded – If
soft_time_limitelapses and the workflow does not exit before the hard deadline. Catch to save partial work / write a checkpoint / exit cleanly.HardTimeLimitExceeded – If
time_limit + graceelapses regardless of soft-limit acknowledgement. Operators rely on this path to bound runaway resource consumption.
- Return type:
Time-Limit Example (celery-style soft-then-hard contract):
from kailash.runtime.local import LocalRuntime from kailash.sdk_exceptions import ( SoftTimeLimitExceeded, HardTimeLimitExceeded, ) runtime = LocalRuntime() try: results, run_id = runtime.execute( workflow.build(), soft_time_limit=2.0, # warn-and-raise (catchable) time_limit=5.0, # hard kill after grace ) except SoftTimeLimitExceeded: # Save partial work, write a checkpoint, return early. ... except HardTimeLimitExceeded: # Operator-facing: task exceeded the hard kill deadline. ...
- Resource Management:
For proper resource cleanup in long-running applications, use the context manager pattern or call close() explicitly:
- Pattern 1 - Context Manager (Recommended):
>>> with LocalRuntime() as runtime: ... results, run_id = runtime.execute(workflow) # Automatic cleanup
- Pattern 2 - Explicit Close:
>>> runtime = LocalRuntime() >>> try: ... results, run_id = runtime.execute(workflow) ... finally: ... runtime.close()
- Pattern 3 - Automatic (Deprecated):
>>> runtime = LocalRuntime() >>> results, run_id = runtime.execute(workflow) # ⚠️ DeprecationWarning # Cleanup on process exit (atexit)
- Deprecation Notice:
Using LocalRuntime without context manager or explicit close() is deprecated and will emit a DeprecationWarning. This pattern will raise an error in v0.12.0. Please migrate to context manager pattern.
Examples
- Sequential workflows with context manager:
>>> with LocalRuntime() as runtime: ... results1, _ = runtime.execute(workflow1) ... results2, _ = runtime.execute(workflow2) # Same event loop! ... results3, _ = runtime.execute(workflow3) # Same event loop!
- Long-running service:
>>> class DataProcessor: ... def __init__(self): ... self.runtime = LocalRuntime() ... def process(self, workflow): ... return self.runtime.execute(workflow) ... def shutdown(self): ... self.runtime.close()
See also
close(): Explicit cleanup
__enter__, __exit__: Context manager support
execute_async(): Async variant
- async execute_async(workflow: Workflow, task_manager: TaskManager | None = None, parameters: dict[str, dict[str, Any]] | dict[str, Any] | None = None, cancellation_token: CancellationToken | None = None, execution_tracker: ExecutionTracker | None = None, search_attributes: Dict[str, Any] | None = None, *, idempotency_key: str | None = None, force_resume_with_drift: bool = False) tuple[dict[str, Any], str | None][source]
Execute a workflow asynchronously (for AsyncLocalRuntime compatibility).
- Parameters:
workflow (Workflow) – Workflow to execute.
task_manager (TaskManager | None) – Optional task manager for tracking.
parameters (dict[str, dict[str, Any]] | dict[str, Any] | None) – Optional parameter overrides per node.
cancellation_token (CancellationToken | None) – Optional token to request cancellation.
execution_tracker (ExecutionTracker | None) – Optional tracker for checkpoint/restore. When provided, completed nodes are skipped and their cached outputs are replayed. New completions are recorded into the tracker for subsequent checkpoint captures.
search_attributes (Dict[str, Any] | None) – Optional typed key-value pairs for indexing and querying workflow runs.
idempotency_key (str | None)
force_resume_with_drift (bool)
- Returns:
Tuple of (results dict, run_id).
- Raises:
RuntimeExecutionError – If execution fails.
WorkflowValidationError – If workflow is invalid.
WorkflowCancelledError – If cancellation is requested.
PermissionError – If access control denies execution.
- Return type:
- signal(workflow_id: str, signal_name: str, data: Any = None) None[source]
Send a signal to a running workflow.
Delivers a named signal with optional data to a workflow identified by its run_id (or workflow_id). If the workflow has a SignalWaitNode waiting for this signal, it will receive the data and resume.
- Parameters:
- Raises:
KeyError – If no workflow with the given ID is currently active.
- Return type:
None
Example
>>> runtime = LocalRuntime() >>> # After starting a workflow with a SignalWaitNode: >>> runtime.signal(run_id, "approval", {"approved": True})
- async query(workflow_id: str, query_name: str, **kwargs: Any) Any[source]
Query the state of a running workflow.
Executes a registered query handler on the target workflow. Query handlers are registered by nodes or the runtime via the QueryRegistry.
- Parameters:
- Returns:
The return value of the query handler.
- Raises:
KeyError – If no workflow with the given ID is active, or if no handler is registered for the given query name.
- Return type:
Example
>>> result = await runtime.query(run_id, "progress") >>> print(result) # {"completed": 5, "total": 10}
- get_signal_channel(workflow_id: str) SignalChannel | None[source]
Get the SignalChannel for a running workflow.
- Parameters:
workflow_id (str) – The run_id or workflow_id of the target workflow.
- Returns:
The SignalChannel instance, or None if no workflow is active.
- Return type:
SignalChannel | None
- get_query_registry(workflow_id: str) QueryRegistry | None[source]
Get the QueryRegistry for a running workflow.
- Parameters:
workflow_id (str) – The run_id or workflow_id of the target workflow.
- Returns:
The QueryRegistry instance, or None if no workflow is active.
- Return type:
QueryRegistry | None
- property progress_registry: ProgressRegistry
Registry for progress callbacks during workflow execution.
Register callbacks to receive ProgressUpdate events from nodes:
>>> runtime = LocalRuntime() >>> runtime.progress_registry.register(lambda u: print(u.message))
- property shutdown_coordinator: ShutdownCoordinator
Get or lazily create the ShutdownCoordinator for this runtime.
The coordinator is created on first access and the runtime’s own cleanup is automatically registered at priority 1 (drain).
- Returns:
ShutdownCoordinator instance associated with this runtime.
Example
>>> runtime = LocalRuntime() >>> runtime.shutdown_coordinator.register("db", pool.close, priority=3) >>> await runtime.shutdown_coordinator.shutdown()
- mark_externally_managed() LocalRuntime[source]
Declare that an owning framework manages this runtime’s lifecycle.
Frameworks that hold a long-lived
LocalRuntimeacross manyexecute()calls (e.g. DataFlow’sModelRegistry,DataFlowinstance, migration inspectors) should call this method immediately after construction. The runtime will then:NOT emit the “use context manager”
DeprecationWarningonexecute()— that warning targets transient ad-hoc callers, not frameworks with their own shutdown protocol.NOT register an
atexitcleanup handler for the persistent event loop — the owner is responsible for callingclose()at its own shutdown.
The caller MUST invoke
close()(or route cleanup throughShutdownCoordinator) when the owning framework tears down.- Returns:
selfto support fluent construction:self.runtime = LocalRuntime().mark_externally_managed()
- Return type:
See also
Issue #478 — the original DataFlow internal-warning leak that motivated this public opt-out.
close()— the cleanup call the owner is now responsible for.
- close() None[source]
Explicitly close the runtime and clean up resources.
This method should be called when you’re done with the runtime instance, especially in long-running applications. It closes the persistent event loop and releases all associated resources, including: - Event loop - Pending async tasks - Connection pools (indirectly, via loop closure)
Usage Patterns:
- Pattern 1 - Try/Finally (Explicit Control):
>>> runtime = LocalRuntime() >>> try: ... results, run_id = runtime.execute(workflow) ... finally: ... runtime.close() # Always clean up
- Pattern 2 - Long-Running Service:
>>> class MyService: ... def __init__(self): ... self.runtime = LocalRuntime() ... def shutdown(self): ... self.runtime.close()
- Pattern 3 - Context Manager (Recommended):
>>> with LocalRuntime() as runtime: ... results = runtime.execute(workflow) # Automatic cleanup (close() called by __exit__)
Note
After calling close(), the runtime can still be used - a new event loop will be created automatically on the next execute() call. However, for best practices, create a new runtime instance instead of reusing after close().
- Thread Safety:
Safe to call from any thread. Protected by internal lock.
- Idempotency:
Safe to call multiple times. Subsequent calls are no-ops.
Examples
>>> runtime = LocalRuntime() >>> runtime.execute(workflow1) >>> runtime.close() # Clean up >>> runtime.execute(workflow2) # New loop created (OK but not recommended) >>> runtime.close() # Safe to call again
See also
__enter__, __exit__: Context manager support
_cleanup_event_loop: Internal cleanup implementation
- Return type:
None
- acquire() LocalRuntime[source]
Increment reference count. Call when sharing this runtime.
- Returns self for fluent usage:
subsystem = Subsystem(runtime=shared_runtime.acquire())
- Raises:
RuntimeError – If the runtime has already been fully closed (ref_count <= 0).
- Return type:
- release() None[source]
Decrement reference count. Alias for close().
Actual cleanup happens when count reaches 0.
- Return type:
None
- __del__(_warnings: ModuleType = <module 'warnings' from '/opt/hostedtoolcache/Python/3.11.15/x64/lib/python3.11/warnings.py'>) None[source]
Emit ResourceWarning if runtime was not properly closed.
- Parameters:
_warnings (ModuleType)
- Return type:
None
- __enter__() LocalRuntime[source]
Enter context manager, ensuring event loop is created.
This method is called when entering a ‘with’ statement. It: 1. Marks the runtime as context-managed 2. Eagerly creates the persistent event loop 3. Returns self for with-statement binding
- Usage:
>>> with LocalRuntime() as runtime: ... results, run_id = runtime.execute(workflow1) ... results2, run_id2 = runtime.execute(workflow2) # Same loop! # Automatic cleanup on exit (__exit__ called)
- Context Management Benefits:
Automatic cleanup (even on exceptions)
Clear resource lifetime
No atexit fallback needed
Pythonic and explicit
- Returns:
Self for with-statement binding
- Return type:
Examples
>>> with LocalRuntime(debug=True, enable_cycles=True) as runtime: ... for workflow in workflow_list: ... results, run_id = runtime.execute(workflow) # All workflows share same event loop, then cleanup
Note
The event loop is created eagerly in __enter__, not lazily in execute(). This ensures consistent behavior regardless of execution paths.
See also
__exit__: Cleanup counterpart
close(): Explicit cleanup without context manager
- __exit__(exc_type: type | None, exc_val: BaseException | None, exc_tb: Any | None) None[source]
Exit context manager, cleaning up event loop.
This method is called when exiting a ‘with’ statement. It: 1. Cleans up the persistent event loop 2. Resets context-managed flag 3. Returns False to propagate exceptions
- Parameters:
exc_type (type | None) – Exception type if exception occurred in with-block
exc_val (BaseException | None) – Exception value if exception occurred
exc_tb (Any | None) – Exception traceback if exception occurred
- Returns:
False (do not suppress exceptions)
- Return type:
None
- Exception Handling:
This method does NOT suppress exceptions from the with-block. If an exception occurs during workflow execution, it will be propagated AFTER cleanup completes.
Examples
>>> with LocalRuntime() as runtime: ... results = runtime.execute(workflow) ... raise ValueError("Test") # Exception raised # __exit__ called with exc_type=ValueError # Cleanup happens, then ValueError propagated
Note
Cleanup happens even if an exception occurred. The event loop is guaranteed to be cleaned up regardless of execution success.
See also
__enter__: Entry counterpart
_cleanup_event_loop: Actual cleanup implementation
- get_validation_metrics() Dict[str, Any][source]
Get validation performance metrics for the runtime.
- async execute_node_with_enterprise_features(node, node_id: str, inputs: dict[str, Any], **execution_kwargs) Any[source]
Execute a node with automatic enterprise feature integration.
This method automatically applies: - Resource limit enforcement - Retry policies with circuit breaker integration - Performance monitoring - Error handling and recovery
- Parameters:
- Returns:
Node execution result
- Raises:
Various enterprise exceptions based on configured policies –
- Return type:
- execute_node_with_enterprise_features_sync(node, node_id: str, inputs: dict[str, Any], **execution_kwargs) Any[source]
Execute a node with automatic enterprise features (synchronous version).
This is the sync wrapper for enterprise features that can be called from the CyclicWorkflowExecutor which runs in sync context.
- get_resource_metrics() dict[str, Any] | None[source]
Get current resource usage metrics from the resource enforcer.
- get_execution_metrics(run_id: str) dict[str, Any] | None[source]
Get execution metrics for a specific run ID.
- get_retry_policy_engine()[source]
Get the retry policy engine instance.
- Returns:
RetryPolicyEngine instance or None if not initialized
- get_retry_analytics()[source]
Get comprehensive retry analytics and metrics.
- Returns:
Dictionary containing retry analytics or None if retry engine not enabled
- get_retry_metrics_summary()[source]
Get summary of retry metrics.
- Returns:
Dictionary containing retry metrics summary or None if not available
- get_strategy_effectiveness()[source]
Get effectiveness statistics for all retry strategies.
- Returns:
Dictionary mapping strategy names to effectiveness stats
- register_retry_strategy(name: str, strategy)[source]
Register a custom retry strategy.
- Parameters:
name (str) – Strategy name for identification
strategy – RetryStrategy instance
- register_retry_strategy_for_exception(exception_type: type, strategy)[source]
Register strategy for specific exception type.
- Parameters:
exception_type (type) – Exception type to handle
strategy – RetryStrategy to use for this exception type
- add_retriable_exception(exception_type: type)[source]
Add an exception type to the retriable exceptions list.
- Parameters:
exception_type (type) – Exception type to mark as retriable
- add_non_retriable_exception(exception_type: type)[source]
Add an exception type to the non-retriable exceptions list.
- Parameters:
exception_type (type) – Exception type to mark as non-retriable
- get_retry_configuration()[source]
Get current retry policy configuration.
- Returns:
Dictionary containing current retry configuration
- get_execution_plan_cached(workflow: Workflow, switch_results: Dict[str, Dict[str, Any]]) List[str] | tuple[str, ...][source]
Get execution plan with caching for improved performance.
- get_execution_analytics() Dict[str, Any][source]
Get comprehensive execution analytics for monitoring and optimization.
- record_execution_performance(workflow: Workflow, execution_time: float, nodes_executed: int, used_conditional: bool, performance_improvement: float = 0.0)[source]
Record execution performance for analytics.
- Parameters:
workflow (Workflow) – Workflow that was executed
execution_time (float) – Total execution time in seconds
nodes_executed (int) – Number of nodes actually executed
used_conditional (bool) – Whether conditional execution was used
performance_improvement (float) – Performance improvement percentage (0.0-1.0)
- clear_analytics_data(keep_patterns: bool = True)[source]
Clear analytics data for fresh monitoring.
- Parameters:
keep_patterns (bool) – Whether to preserve execution patterns
- get_health_diagnostics() Dict[str, Any][source]
Get health diagnostics for monitoring system health.
- optimize_runtime_performance() Dict[str, Any][source]
Optimize runtime performance based on analytics data.
- generate_compatibility_report(workflow: Workflow) Dict[str, Any][source]
Generate compatibility report for a workflow.
- get_compatibility_report_markdown(workflow: Workflow) str[source]
Generate compatibility report in markdown format.
- set_performance_monitoring(enabled: bool) None[source]
Enable or disable performance monitoring.
- Parameters:
enabled (bool) – Whether to enable performance monitoring
- Return type:
None
- set_automatic_mode_switching(enabled: bool) None[source]
Enable or disable automatic mode switching based on performance.
- Parameters:
enabled (bool) – Whether to enable automatic switching
- Return type:
None
- set_compatibility_reporting(enabled: bool) None[source]
Enable or disable compatibility reporting.
- Parameters:
enabled (bool) – Whether to enable compatibility reporting
- Return type:
None
- get_execution_path_debug_info() Dict[str, Any][source]
Get detailed debug information about execution paths.
- async start_persistent_mode() None[source]
Start runtime in persistent mode for long-running applications.
This enables connection pool sharing, resource coordination, and enterprise monitoring features. Only available when persistent_mode=True.
- Raises:
RuntimeError – If persistent mode is not enabled or startup fails.
- Return type:
None
- async shutdown_gracefully(timeout: int = 30) None[source]
Gracefully shutdown runtime with connection drain and cleanup.
- Parameters:
timeout (int) – Maximum time to wait for shutdown completion (seconds).
- Return type:
None
Get shared connection pool for database operations.
- Parameters:
- Returns:
Connection pool instance
- Raises:
RuntimeError – If persistent mode is not started
ValueError – If pool configuration is invalid
- Return type:
- can_execute_workflow() bool[source]
Check if runtime can execute another workflow based on limits.
- Returns:
True if workflow can be executed, False otherwise.
- Return type:
- get_runtime_metrics() Dict[str, Any][source]
Get comprehensive runtime health and performance metrics.
- property connection_pool_manager
Access the connection pool manager.
- __repr__() str
Get string representation of runtime instance.
- Returns:
String representation with key configuration
- Return type:
Example
>>> runtime = LocalRuntime(debug=True, enable_cycles=True) >>> print(repr(runtime)) <LocalRuntime(id=runtime_..., debug=True, cycles=True, async=False)>
- validate_workflow(workflow: Workflow) list[str]
Validate a workflow before execution.
Performs comprehensive validation including: - Basic workflow structure validation - Disconnected node detection - Required parameter checking - Connection validation - Performance warnings for large workflows
EXTRACTED FROM: LocalRuntime.validate_workflow() (lines 2054-2119) SHARED LOGIC: 100% - Pure validation with no I/O
- Parameters:
workflow (Workflow) – Workflow to validate
- Returns:
List of validation warnings (empty if valid)
- Raises:
WorkflowValidationError – If workflow is invalid
- Return type:
- Implementation Notes:
Calls workflow.validate() for basic structure
Checks for disconnected nodes in multi-node workflows
Validates required parameters are provided or connected
Warns about performance implications for large workflows
All logic is synchronous and shared between runtimes
Examples
# Basic validation runtime = LocalRuntime() warnings = runtime.validate_workflow(workflow) if warnings: for warning in warnings: print(f"Warning: {warning}") # Validation with error handling try: warnings = runtime.validate_workflow(workflow) if not warnings: print("Workflow is valid") except WorkflowValidationError as e: print(f"Invalid workflow: {e}")
- logger
Validation capabilities for workflow runtimes.
This mixin provides comprehensive validation logic for workflows, nodes, parameters, and connections. All methods are 100% shared between sync and async runtimes as they perform pure validation with no I/O operations.
- Shared Logic (100%):
All validation methods are pure logic with no sync/async variants needed. They validate data structures and configurations without performing any I/O or execution.
- Dependencies:
Requires workflow.graph attribute (from Workflow)
Requires self.logger attribute (from BaseRuntime)
Requires self.debug attribute (from BaseRuntime)
No dependencies on other mixins
- Usage:
- class LocalRuntime(BaseRuntime, ValidationMixin):
# Inherits all 5 validation methods pass
- class AsyncLocalRuntime(BaseRuntime, ValidationMixin):
# Inherits same 5 validation methods pass
Examples
# Validation is called automatically during execution runtime = LocalRuntime() runtime.execute(workflow) # Calls validation methods
# Can also validate manually warnings = runtime.validate_workflow(workflow) runtime._validate_connection_contracts(workflow, node_id, inputs, outputs)
See also
BaseRuntime: Base runtime class
ADR-XXX: Runtime Refactoring for Feature Parity
- Version:
Added in: v0.10.0 Part of: Runtime parity remediation
- debug
- enable_cycles
- enable_monitoring
- conditional_execution
Characteristics:
Executes nodes sequentially
Good for debugging and development
Lower overhead for simple workflows
Direct access to local filesystem
Example Usage:
from kailash.runtime import LocalRuntime
from kailash import Workflow
workflow = Workflow("local_example")
# Add nodes...
# Execute with local runtime
with LocalRuntime() as runtime:
results = runtime.execute(workflow)
Connection Parameter Validation (v0.8.4+)
Configure connection parameter validation modes for enhanced security:
from kailash.runtime import LocalRuntime
# Development mode - strict validation
runtime = LocalRuntime(
connection_validation="strict" # Block invalid connection parameters
)
# Production mode - warnings only (default)
runtime = LocalRuntime(
connection_validation="warn" # Log warnings but continue
)
# Performance mode - no validation
runtime = LocalRuntime(
connection_validation="off" # No validation overhead
)
Connection Validation Modes:
off: No connection parameter validation
warn: Log warnings for parameter issues but continue execution (default)
strict: Block execution for invalid connection parameters
Performance Characteristics:
Validation Overhead: <1ms per workflow execution
Connection Security: Prevents parameter injection through workflow connections
Enterprise Ready: Comprehensive metrics collection and alerting
Validation Features:
from kailash.runtime import LocalRuntime
# Create runtime with validation
with LocalRuntime(connection_validation="strict") as runtime:
# Execute workflow - validation runs automatically
results, run_id = runtime.execute(workflow, parameters=params)
# Access validation metrics
metrics = runtime.get_validation_metrics()
print(f"Performance: {metrics['performance_summary']}")
print(f"Security: {metrics['security_report']}")
# Reset metrics for next run
runtime.reset_validation_metrics()
AsyncLocalRuntime
Asynchronous runtime for I/O-bound operations.
- class kailash.runtime.async_local.AsyncLocalRuntime(resource_registry: ResourceRegistry | None = None, max_concurrent_nodes: int = 10, enable_analysis: bool = True, enable_profiling: bool = True, thread_pool_size: int = 4, execution_timeout: int | None = None, **kwargs)[source]
Bases:
LocalRuntimeAsync-optimized runtime for Kailash workflows.
Extends LocalRuntime with advanced async execution capabilities while inheriting all enterprise features through shared mixin architecture.
- Inherits from:
- LocalRuntime: Provides 100% feature parity with sync runtime
├─ BaseRuntime: Core runtime foundation and configuration ├─ CycleExecutionMixin: Cyclic workflow execution delegation ├─ ValidationMixin: Workflow validation and contract checking └─ ConditionalExecutionMixin: Conditional execution and branching logic
- Async-Specific Extensions:
WorkflowAnalyzer: Analyzes workflows for optimization opportunities
ExecutionContext: Async context with integrated resource access
Level-based parallel execution: Executes independent nodes concurrently
Semaphore-based concurrency control: Limits concurrent node execution
Thread pool for sync nodes: Executes sync nodes without blocking async loop
Advanced performance tracking: Detailed metrics collection
- Execution Strategies:
The runtime automatically selects the optimal execution strategy: - Pure async: All nodes are async (fastest, full concurrency) - Mixed: Combination of sync and async nodes (balanced) - Sync in thread pool: All sync nodes (compatibility mode)
Example
from kailash.resources import ResourceRegistry, DatabasePoolFactory from kailash.runtime.async_local import AsyncLocalRuntime # Setup resources registry = ResourceRegistry() registry.register_factory("db", DatabasePoolFactory(...)) # Create async runtime runtime = AsyncLocalRuntime( resource_registry=registry, max_concurrent_nodes=10, enable_analysis=True ) # Execute workflow result = await runtime.execute_workflow_async(workflow, inputs)
- Parameters:
- __init__(resource_registry: ResourceRegistry | None = None, max_concurrent_nodes: int = 10, enable_analysis: bool = True, enable_profiling: bool = True, thread_pool_size: int = 4, execution_timeout: int | None = None, **kwargs)[source]
Initialize AsyncLocalRuntime.
- Parameters:
resource_registry (ResourceRegistry | None) – Optional ResourceRegistry for resource management
max_concurrent_nodes (int) – Maximum number of nodes to execute concurrently
enable_analysis (bool) – Whether to analyze workflows for optimization
enable_profiling (bool) – Whether to collect detailed performance metrics
thread_pool_size (int) – Size of thread pool for sync node execution
execution_timeout (int | None) – Workflow execution timeout in seconds (default: 300 or DATAFLOW_EXECUTION_TIMEOUT env var)
**kwargs – Additional arguments passed to LocalRuntime
- property execution_semaphore: Semaphore
Lazily create execution semaphore when accessed.
P0-7 FIX: Semaphore must be created in async context (with running event loop). Creating in __init__ causes race conditions in FastAPI/Docker deployments.
- execute(workflow, task_manager: TaskManager | None = None, parameters: Dict[str, Any] | None = None, cancellation_token: Any = None, search_attributes: Dict[str, Any] | None = None, *, soft_time_limit: float | None = None, time_limit: float | None = None, **kwargs: Any) Tuple[Dict[str, Any], str | None][source]
Execute workflow without creating threads (Docker-safe).
This override prevents the parent’s threading-based execution that causes Docker file descriptor issues. Uses pure async execution via asyncio.run or returns the async task if already in an event loop.
- Parameters:
workflow – Workflow to execute
task_manager (TaskManager | None) – Optional task manager for tracking
parameters (Dict[str, Any] | None) – Input parameters for the workflow
soft_time_limit (float | None) – Optional advisory deadline in seconds (#912). Raises
SoftTimeLimitExceededwhen reached; user code MAY catch and exit cleanly.time_limit (float | None) – Optional unconditional kill deadline in seconds (#912). Raises
HardTimeLimitExceededaftertime_limit + graceregardless of acknowledgement.cancellation_token (Any)
kwargs (Any)
- Returns:
Tuple of (results dict, run_id)
- Raises:
RuntimeError – If called from async context (use execute_workflow_async instead)
SoftTimeLimitExceeded – If
soft_time_limitelapses.HardTimeLimitExceeded – If
time_limit + graceelapses.
- Return type:
Time-Limit Example:
from kailash.runtime.async_local import AsyncLocalRuntime from kailash.sdk_exceptions import SoftTimeLimitExceeded runtime = AsyncLocalRuntime() try: results, run_id = runtime.execute( workflow.build(), soft_time_limit=2.0, time_limit=5.0, ) except SoftTimeLimitExceeded: ... # save partial work, exit cleanly
- async execute_async(workflow, task_manager: TaskManager | None = None, parameters: Dict[str, Any] | None = None, cancellation_token: Any = None, execution_tracker: Any = None, search_attributes: Dict[str, Any] | None = None, **kwargs: Any) Tuple[Dict[str, Any], str | None][source]
Execute workflow asynchronously (for LocalRuntime compatibility).
This method provides compatibility with LocalRuntime’s execute_async() interface while using AsyncLocalRuntime’s execution engine.
- Parameters:
- Returns:
Tuple of (results dict, run_id)
- Return type:
- async execute_workflow_async(workflow, inputs: Dict[str, Any], context: ExecutionContext | None = None, *, idempotency_key: str | None = None, force_resume_with_drift: bool = False, soft_time_limit: float | None = None, time_limit: float | None = None) Tuple[Dict[str, Any], str][source]
Execute workflow with native async support and production safeguards.
P0 Component 1 Features: - Timeout protection (configurable via execution_timeout) - Connection lifecycle management - Task cancellation on timeout - Cleanup guarantees
This method provides first-class async execution with: - Concurrent node execution where dependencies allow - Integrated resource management - Performance optimization based on workflow analysis - Advanced error handling and recovery
- Parameters:
workflow – Workflow to execute
context (ExecutionContext | None) – Optional execution context
soft_time_limit (float | None) – Optional advisory deadline in seconds (#912). Raises
SoftTimeLimitExceededwhen reached; user code MAY catch and exit cleanly.time_limit (float | None) – Optional unconditional kill deadline in seconds (#912). Raises
HardTimeLimitExceededaftertime_limit + grace.idempotency_key (str | None)
force_resume_with_drift (bool)
- Returns:
Tuple of (results dict, run_id) - For compatibility with tests - results: Dictionary mapping node_id -> node output - run_id: Unique execution identifier
- Return type:
Note
Returns tuple for compatibility with LocalRuntime.execute() pattern. Existing tests may expect dict - use results, run_id = await execute_workflow_async()
- Raises:
asyncio.TimeoutError – If execution exceeds configured timeout
WorkflowExecutionError – If execution fails
SoftTimeLimitExceeded – If
soft_time_limitelapses.HardTimeLimitExceeded – If
time_limit + graceelapses.
- Parameters:
- Return type:
Time-Limit Example (async):
from kailash.runtime.async_local import AsyncLocalRuntime from kailash.sdk_exceptions import SoftTimeLimitExceeded runtime = AsyncLocalRuntime() try: results, run_id = await runtime.execute_workflow_async( workflow.build(), inputs={}, soft_time_limit=2.0, time_limit=5.0, ) except SoftTimeLimitExceeded: ... # save partial work, exit cleanly
- async cleanup() None[source]
Clean up runtime resources (idempotent).
P0-8 FIX: Enhanced cleanup with proper resource management. Safe to call multiple times - tracks cleanup state.
Recommended usage with a web framework lifespan:
from contextlib import asynccontextmanager from fastapi import FastAPI @asynccontextmanager async def lifespan(app: FastAPI): # Startup runtime = AsyncLocalRuntime() yield {"runtime": runtime} # Shutdown await runtime.cleanup() app = FastAPI(lifespan=lifespan)
- Return type:
None
- close() None[source]
Synchronous close that properly cleans up ALL async resources.
Overrides LocalRuntime.close() to also handle thread pool, resource registry, semaphore, and SQL connection pools.
Reference-count aware: decrements _ref_count. Actual cleanup only happens when _ref_count reaches 0.
- Return type:
None
- __del__(_warnings: ModuleType = <module 'warnings' from '/opt/hostedtoolcache/Python/3.11.15/x64/lib/python3.11/warnings.py'>) None[source]
Emit ResourceWarning if runtime was not properly closed.
- Parameters:
_warnings (ModuleType)
- Return type:
None
- async __aenter__() AsyncLocalRuntime[source]
Async context manager entry.
- Usage:
- async with AsyncLocalRuntime() as runtime:
results = await runtime.execute_workflow_async(workflow, inputs)
- Return type:
- __enter__() LocalRuntime
Enter context manager, ensuring event loop is created.
This method is called when entering a ‘with’ statement. It: 1. Marks the runtime as context-managed 2. Eagerly creates the persistent event loop 3. Returns self for with-statement binding
- Usage:
>>> with LocalRuntime() as runtime: ... results, run_id = runtime.execute(workflow1) ... results2, run_id2 = runtime.execute(workflow2) # Same loop! # Automatic cleanup on exit (__exit__ called)
- Context Management Benefits:
Automatic cleanup (even on exceptions)
Clear resource lifetime
No atexit fallback needed
Pythonic and explicit
- Returns:
Self for with-statement binding
- Return type:
Examples
>>> with LocalRuntime(debug=True, enable_cycles=True) as runtime: ... for workflow in workflow_list: ... results, run_id = runtime.execute(workflow) # All workflows share same event loop, then cleanup
Note
The event loop is created eagerly in __enter__, not lazily in execute(). This ensures consistent behavior regardless of execution paths.
See also
__exit__: Cleanup counterpart
close(): Explicit cleanup without context manager
- __exit__(exc_type: type | None, exc_val: BaseException | None, exc_tb: Any | None) None
Exit context manager, cleaning up event loop.
This method is called when exiting a ‘with’ statement. It: 1. Cleans up the persistent event loop 2. Resets context-managed flag 3. Returns False to propagate exceptions
- Parameters:
exc_type (type | None) – Exception type if exception occurred in with-block
exc_val (BaseException | None) – Exception value if exception occurred
exc_tb (Any | None) – Exception traceback if exception occurred
- Returns:
False (do not suppress exceptions)
- Return type:
None
- Exception Handling:
This method does NOT suppress exceptions from the with-block. If an exception occurs during workflow execution, it will be propagated AFTER cleanup completes.
Examples
>>> with LocalRuntime() as runtime: ... results = runtime.execute(workflow) ... raise ValueError("Test") # Exception raised # __exit__ called with exc_type=ValueError # Cleanup happens, then ValueError propagated
Note
Cleanup happens even if an exception occurred. The event loop is guaranteed to be cleaned up regardless of execution success.
See also
__enter__: Entry counterpart
_cleanup_event_loop: Actual cleanup implementation
- __repr__() str
Get string representation of runtime instance.
- Returns:
String representation with key configuration
- Return type:
Example
>>> runtime = LocalRuntime(debug=True, enable_cycles=True) >>> print(repr(runtime)) <LocalRuntime(id=runtime_..., debug=True, cycles=True, async=False)>
- acquire() LocalRuntime
Increment reference count. Call when sharing this runtime.
- Returns self for fluent usage:
subsystem = Subsystem(runtime=shared_runtime.acquire())
- Raises:
RuntimeError – If the runtime has already been fully closed (ref_count <= 0).
- Return type:
- add_non_retriable_exception(exception_type: type)
Add an exception type to the non-retriable exceptions list.
- Parameters:
exception_type (type) – Exception type to mark as non-retriable
- add_retriable_exception(exception_type: type)
Add an exception type to the retriable exceptions list.
- Parameters:
exception_type (type) – Exception type to mark as retriable
- can_execute_workflow() bool
Check if runtime can execute another workflow based on limits.
- Returns:
True if workflow can be executed, False otherwise.
- Return type:
- clear_analytics_data(keep_patterns: bool = True)
Clear analytics data for fresh monitoring.
- Parameters:
keep_patterns (bool) – Whether to preserve execution patterns
- property connection_pool_manager
Access the connection pool manager.
- async execute_node_with_enterprise_features(node, node_id: str, inputs: dict[str, Any], **execution_kwargs) Any
Execute a node with automatic enterprise feature integration.
This method automatically applies: - Resource limit enforcement - Retry policies with circuit breaker integration - Performance monitoring - Error handling and recovery
- Parameters:
- Returns:
Node execution result
- Raises:
Various enterprise exceptions based on configured policies –
- Return type:
- execute_node_with_enterprise_features_sync(node, node_id: str, inputs: dict[str, Any], **execution_kwargs) Any
Execute a node with automatic enterprise features (synchronous version).
This is the sync wrapper for enterprise features that can be called from the CyclicWorkflowExecutor which runs in sync context.
- generate_compatibility_report(workflow: Workflow) Dict[str, Any]
Generate compatibility report for a workflow.
- get_compatibility_report_markdown(workflow: Workflow) str
Generate compatibility report in markdown format.
- get_execution_analytics() Dict[str, Any]
Get comprehensive execution analytics for monitoring and optimization.
- get_execution_metrics(run_id: str) dict[str, Any] | None
Get execution metrics for a specific run ID.
- get_execution_path_debug_info() Dict[str, Any]
Get detailed debug information about execution paths.
- get_execution_plan_cached(workflow: Workflow, switch_results: Dict[str, Dict[str, Any]]) List[str] | tuple[str, ...]
Get execution plan with caching for improved performance.
- get_query_registry(workflow_id: str) QueryRegistry | None
Get the QueryRegistry for a running workflow.
- Parameters:
workflow_id (str) – The run_id or workflow_id of the target workflow.
- Returns:
The QueryRegistry instance, or None if no workflow is active.
- Return type:
QueryRegistry | None
- get_resource_metrics() dict[str, Any] | None
Get current resource usage metrics from the resource enforcer.
- get_retry_analytics()
Get comprehensive retry analytics and metrics.
- Returns:
Dictionary containing retry analytics or None if retry engine not enabled
- get_retry_configuration()
Get current retry policy configuration.
- Returns:
Dictionary containing current retry configuration
- get_retry_metrics_summary()
Get summary of retry metrics.
- Returns:
Dictionary containing retry metrics summary or None if not available
- get_retry_policy_engine()
Get the retry policy engine instance.
- Returns:
RetryPolicyEngine instance or None if not initialized
Get shared connection pool for database operations.
- Parameters:
- Returns:
Connection pool instance
- Raises:
RuntimeError – If persistent mode is not started
ValueError – If pool configuration is invalid
- Return type:
- get_signal_channel(workflow_id: str) SignalChannel | None
Get the SignalChannel for a running workflow.
- Parameters:
workflow_id (str) – The run_id or workflow_id of the target workflow.
- Returns:
The SignalChannel instance, or None if no workflow is active.
- Return type:
SignalChannel | None
- get_strategy_effectiveness()
Get effectiveness statistics for all retry strategies.
- Returns:
Dictionary mapping strategy names to effectiveness stats
- mark_externally_managed() LocalRuntime
Declare that an owning framework manages this runtime’s lifecycle.
Frameworks that hold a long-lived
LocalRuntimeacross manyexecute()calls (e.g. DataFlow’sModelRegistry,DataFlowinstance, migration inspectors) should call this method immediately after construction. The runtime will then:NOT emit the “use context manager”
DeprecationWarningonexecute()— that warning targets transient ad-hoc callers, not frameworks with their own shutdown protocol.NOT register an
atexitcleanup handler for the persistent event loop — the owner is responsible for callingclose()at its own shutdown.
The caller MUST invoke
close()(or route cleanup throughShutdownCoordinator) when the owning framework tears down.- Returns:
selfto support fluent construction:self.runtime = LocalRuntime().mark_externally_managed()
- Return type:
See also
Issue #478 — the original DataFlow internal-warning leak that motivated this public opt-out.
close()— the cleanup call the owner is now responsible for.
- on_node_complete(callback: Callable[[NodeCompletionEvent], None | Awaitable[None]]) Any
Register callback for every node completion this runtime emits.
Returns an unregister function — call it to remove the callback.
Subscribers see one
NodeCompletionEventper node, regardless of whether the node succeeded or failed; the event’serrorfield carries the failure repr when set. The runtime appliesredact_event_for_persistence()to the event BEFORE dispatch so no subscriber ever observes a classified PK or a redacted field’s raw value.Subscriber exceptions are caught and logged at WARN level — a misbehaving subscriber MUST NOT take down workflow execution.
- Parameters:
callback (Callable[[NodeCompletionEvent], None | Awaitable[None]])
- Return type:
- optimize_runtime_performance() Dict[str, Any]
Optimize runtime performance based on analytics data.
- property progress_registry: ProgressRegistry
Registry for progress callbacks during workflow execution.
Register callbacks to receive ProgressUpdate events from nodes:
>>> runtime = LocalRuntime() >>> runtime.progress_registry.register(lambda u: print(u.message))
- async query(workflow_id: str, query_name: str, **kwargs: Any) Any
Query the state of a running workflow.
Executes a registered query handler on the target workflow. Query handlers are registered by nodes or the runtime via the QueryRegistry.
- Parameters:
- Returns:
The return value of the query handler.
- Raises:
KeyError – If no workflow with the given ID is active, or if no handler is registered for the given query name.
- Return type:
Example
>>> result = await runtime.query(run_id, "progress") >>> print(result) # {"completed": 5, "total": 10}
- record_execution_performance(workflow: Workflow, execution_time: float, nodes_executed: int, used_conditional: bool, performance_improvement: float = 0.0)
Record execution performance for analytics.
- Parameters:
workflow (Workflow) – Workflow that was executed
execution_time (float) – Total execution time in seconds
nodes_executed (int) – Number of nodes actually executed
used_conditional (bool) – Whether conditional execution was used
performance_improvement (float) – Performance improvement percentage (0.0-1.0)
- register_retry_strategy(name: str, strategy)
Register a custom retry strategy.
- Parameters:
name (str) – Strategy name for identification
strategy – RetryStrategy instance
- register_retry_strategy_for_exception(exception_type: type, strategy)
Register strategy for specific exception type.
- Parameters:
exception_type (type) – Exception type to handle
strategy – RetryStrategy to use for this exception type
- release() None
Decrement reference count. Alias for close().
Actual cleanup happens when count reaches 0.
- Return type:
None
- reset_retry_metrics()
Reset all retry metrics and analytics data.
- set_automatic_mode_switching(enabled: bool) None
Enable or disable automatic mode switching based on performance.
- Parameters:
enabled (bool) – Whether to enable automatic switching
- Return type:
None
- set_compatibility_reporting(enabled: bool) None
Enable or disable compatibility reporting.
- Parameters:
enabled (bool) – Whether to enable compatibility reporting
- Return type:
None
- set_performance_monitoring(enabled: bool) None
Enable or disable performance monitoring.
- Parameters:
enabled (bool) – Whether to enable performance monitoring
- Return type:
None
- property shutdown_coordinator: ShutdownCoordinator
Get or lazily create the ShutdownCoordinator for this runtime.
The coordinator is created on first access and the runtime’s own cleanup is automatically registered at priority 1 (drain).
- Returns:
ShutdownCoordinator instance associated with this runtime.
Example
>>> runtime = LocalRuntime() >>> runtime.shutdown_coordinator.register("db", pool.close, priority=3) >>> await runtime.shutdown_coordinator.shutdown()
- async shutdown_gracefully(timeout: int = 30) None
Gracefully shutdown runtime with connection drain and cleanup.
- Parameters:
timeout (int) – Maximum time to wait for shutdown completion (seconds).
- Return type:
None
- signal(workflow_id: str, signal_name: str, data: Any = None) None
Send a signal to a running workflow.
Delivers a named signal with optional data to a workflow identified by its run_id (or workflow_id). If the workflow has a SignalWaitNode waiting for this signal, it will receive the data and resume.
- Parameters:
- Raises:
KeyError – If no workflow with the given ID is currently active.
- Return type:
None
Example
>>> runtime = LocalRuntime() >>> # After starting a workflow with a SignalWaitNode: >>> runtime.signal(run_id, "approval", {"approved": True})
- async start_persistent_mode() None
Start runtime in persistent mode for long-running applications.
This enables connection pool sharing, resource coordination, and enterprise monitoring features. Only available when persistent_mode=True.
- Raises:
RuntimeError – If persistent mode is not enabled or startup fails.
- Return type:
None
- validate_workflow(workflow: Workflow) list[str]
Validate a workflow before execution.
Performs comprehensive validation including: - Basic workflow structure validation - Disconnected node detection - Required parameter checking - Connection validation - Performance warnings for large workflows
EXTRACTED FROM: LocalRuntime.validate_workflow() (lines 2054-2119) SHARED LOGIC: 100% - Pure validation with no I/O
- Parameters:
workflow (Workflow) – Workflow to validate
- Returns:
List of validation warnings (empty if valid)
- Raises:
WorkflowValidationError – If workflow is invalid
- Return type:
- Implementation Notes:
Calls workflow.validate() for basic structure
Checks for disconnected nodes in multi-node workflows
Validates required parameters are provided or connected
Warns about performance implications for large workflows
All logic is synchronous and shared between runtimes
Examples
# Basic validation runtime = LocalRuntime() warnings = runtime.validate_workflow(workflow) if warnings: for warning in warnings: print(f"Warning: {warning}") # Validation with error handling try: warnings = runtime.validate_workflow(workflow) if not warnings: print("Workflow is valid") except WorkflowValidationError as e: print(f"Invalid workflow: {e}")
- logger
Validation capabilities for workflow runtimes.
This mixin provides comprehensive validation logic for workflows, nodes, parameters, and connections. All methods are 100% shared between sync and async runtimes as they perform pure validation with no I/O operations.
- Shared Logic (100%):
All validation methods are pure logic with no sync/async variants needed. They validate data structures and configurations without performing any I/O or execution.
- Dependencies:
Requires workflow.graph attribute (from Workflow)
Requires self.logger attribute (from BaseRuntime)
Requires self.debug attribute (from BaseRuntime)
No dependencies on other mixins
- Usage:
- class LocalRuntime(BaseRuntime, ValidationMixin):
# Inherits all 5 validation methods pass
- class AsyncLocalRuntime(BaseRuntime, ValidationMixin):
# Inherits same 5 validation methods pass
Examples
# Validation is called automatically during execution runtime = LocalRuntime() runtime.execute(workflow) # Calls validation methods
# Can also validate manually warnings = runtime.validate_workflow(workflow) runtime._validate_connection_contracts(workflow, node_id, inputs, outputs)
See also
BaseRuntime: Base runtime class
ADR-XXX: Runtime Refactoring for Feature Parity
- Version:
Added in: v0.10.0 Part of: Runtime parity remediation
- debug
- enable_cycles
- cyclic_executor
- enable_monitoring
- conditional_execution
- async __aexit__(exc_type, exc_val, exc_tb) None[source]
Async context manager exit — calls close() which respects ref counting.
Uses close() instead of directly calling cleanup() to ensure the ref counting contract is honored. If this runtime is shared via acquire(), close() will only decrement — not destroy resources.
- Return type:
None
Characteristics:
Concurrent execution of async nodes
Efficient for API calls and file I/O
Better resource utilization
Requires async-compatible nodes
Example Usage:
from kailash.runtime import AsyncLocalRuntime
import asyncio
workflow = Workflow("async_example")
runtime = AsyncLocalRuntime()
# Add async nodes
workflow.add_node("AsyncHTTPClient", "fetch1", config={"url": "..."})
workflow.add_node("AsyncHTTPClient", "fetch2", config={"url": "..."})
# Execute asynchronously
async def run():
try:
results = await runtime.execute_async(workflow)
return results
finally:
runtime.close()
results = asyncio.run(run())
BaseRuntime
The abstract base class all runtimes implement.
- class kailash.runtime.base.BaseRuntime(debug: bool = False, enable_cycles: bool = True, enable_async: bool = True, max_concurrency: int = 10, user_context: Any | None = None, enable_monitoring: bool = True, enable_resource_limits: bool = False, enable_security: bool = False, enable_audit: bool = False, resource_limits: Dict[str, Any] | None = None, secret_provider: Any | None = None, connection_validation: str = 'warn', conditional_execution: str = 'route_data', content_aware_success_detection: bool = True, persistent_mode: bool = False, enable_connection_sharing: bool = True, max_concurrent_workflows: int = 10, connection_pool_size: int = 20, enable_enterprise_monitoring: bool = False, enable_health_monitoring: bool = False, enable_resource_coordination: bool = True, circuit_breaker_config: Dict | None = None, retry_policy_config: Dict | None = None, connection_pool_config: Dict | None = None, trust_context: 'RuntimeTrustContext' | None = None, trust_verifier: Any | None = None, trust_verification_mode: str = 'disabled', audit_generator: Any | None = None, audit_log_to_stdout: bool = False, **kwargs)[source]
Bases:
ABCBase class for all workflow runtimes.
This class provides shared logic that is common to both LocalRuntime (synchronous) and AsyncLocalRuntime (asynchronous), eliminating code duplication and ensuring consistent behavior.
- Architecture:
BaseRuntime provides foundational capabilities: - Configuration validation and initialization - Workflow metadata management - Result tracking and run ID generation - Execution metadata management - Workflow caching - Enterprise feature initialization helpers
Subclasses (LocalRuntime, AsyncLocalRuntime) inherit this base and add runtime-specific execution logic through mixins and concrete implementations.
- Design Pattern:
Follows the SecureGovernedNode mixin pattern established in the SDK: - Base class provides shared initialization via super().__init__() - Subclasses call
super().__init__(**kwargs)to initialize base - Mixins can be added to subclasses for additional capabilities - Abstract methods define the runtime-specific contract- Extracted Logic:
This class extracts ~500 lines of shared logic from LocalRuntime: - Lines 190-350: Configuration initialization and validation - Lines 2100-2200: Enterprise feature helpers (placeholders) - Utility methods for run ID generation, metadata tracking - Workflow caching and state management
- Usage:
This class is not meant to be instantiated directly. Use LocalRuntime or AsyncLocalRuntime instead.
>>> # DON'T: Direct instantiation (will fail - abstract class) >>> runtime = BaseRuntime() # Raises TypeError >>> >>> # DO: Use concrete implementations >>> from kailash.runtime.local import LocalRuntime >>> runtime = LocalRuntime(debug=True, enable_cycles=True) >>> >>> from kailash.runtime.async_local import AsyncLocalRuntime >>> async_runtime = AsyncLocalRuntime(debug=True, enable_async=True)
See also
LocalRuntime: Synchronous workflow execution
AsyncLocalRuntime: Asynchronous workflow execution
ADR-048: Unified Runtime Architecture
ADR-XXX: Runtime Refactoring for Feature Parity
- Version:
Added in: v0.10.0 Part of: Runtime parity remediation (2025-10-25)
- Parameters:
debug (bool)
enable_cycles (bool)
enable_async (bool)
max_concurrency (int)
user_context (Optional[Any])
enable_monitoring (bool)
enable_resource_limits (bool)
enable_security (bool)
enable_audit (bool)
resource_limits (Optional[Dict[str, Any]])
secret_provider (Optional[Any])
connection_validation (str)
conditional_execution (str)
content_aware_success_detection (bool)
persistent_mode (bool)
enable_connection_sharing (bool)
max_concurrent_workflows (int)
connection_pool_size (int)
enable_enterprise_monitoring (bool)
enable_health_monitoring (bool)
enable_resource_coordination (bool)
circuit_breaker_config (Optional[Dict])
retry_policy_config (Optional[Dict])
connection_pool_config (Optional[Dict])
trust_context (Optional['RuntimeTrustContext'])
trust_verifier (Optional[Any])
trust_verification_mode (str)
audit_generator (Optional[Any])
audit_log_to_stdout (bool)
- __init__(debug: bool = False, enable_cycles: bool = True, enable_async: bool = True, max_concurrency: int = 10, user_context: Any | None = None, enable_monitoring: bool = True, enable_resource_limits: bool = False, enable_security: bool = False, enable_audit: bool = False, resource_limits: Dict[str, Any] | None = None, secret_provider: Any | None = None, connection_validation: str = 'warn', conditional_execution: str = 'route_data', content_aware_success_detection: bool = True, persistent_mode: bool = False, enable_connection_sharing: bool = True, max_concurrent_workflows: int = 10, connection_pool_size: int = 20, enable_enterprise_monitoring: bool = False, enable_health_monitoring: bool = False, enable_resource_coordination: bool = True, circuit_breaker_config: Dict | None = None, retry_policy_config: Dict | None = None, connection_pool_config: Dict | None = None, trust_context: 'RuntimeTrustContext' | None = None, trust_verifier: Any | None = None, trust_verification_mode: str = 'disabled', audit_generator: Any | None = None, audit_log_to_stdout: bool = False, **kwargs)[source]
Initialize base runtime.
This method extracts and consolidates common initialization logic from LocalRuntime (lines 190-350), providing a unified foundation for both sync and async runtimes.
- Parameters:
debug (bool) – Whether to enable debug logging.
enable_cycles (bool) – Whether to enable cyclic workflow support.
enable_async (bool) – Whether to enable async execution for async nodes.
max_concurrency (int) – Maximum concurrent async operations.
user_context (Optional[Any]) – User context for access control (optional).
enable_monitoring (bool) – Whether to enable performance monitoring.
enable_security (bool) – Whether to enable security features.
enable_audit (bool) – Whether to enable audit logging.
resource_limits (Optional[Dict[str, Any]]) – Resource limits (memory_mb, cpu_cores, etc.).
secret_provider (Optional[Any]) – Optional secret provider for runtime secret injection.
connection_validation (str) – Connection parameter validation mode: - “off”: No validation (backward compatibility) - “warn”: Log warnings on validation errors (default) - “strict”: Raise errors on validation failures
conditional_execution (str) – Execution strategy for conditional routing: - “route_data”: Current behavior - all nodes execute, data routing only (default) - “skip_branches”: New behavior - skip unreachable branches entirely
content_aware_success_detection (bool) – Whether to enable content-aware success detection: - True: Check return value content for success/failure patterns (default) - False: Only use exception-based failure detection (legacy mode)
persistent_mode (bool) – Whether to enable persistent runtime mode for long-running applications.
enable_connection_sharing (bool) – Whether to enable connection pool sharing across runtime instances.
max_concurrent_workflows (int) – Maximum number of concurrent workflows in persistent mode.
connection_pool_size (int) – Default size for connection pools.
enable_enterprise_monitoring (bool) – Enable enterprise monitoring features.
enable_health_monitoring (bool) – Enable health monitoring.
enable_resource_coordination (bool) – Enable resource coordination.
circuit_breaker_config (Optional[Dict]) – Circuit breaker configuration.
retry_policy_config (Optional[Dict]) – Retry policy configuration.
connection_pool_config (Optional[Dict]) – Connection pool configuration.
trust_context (Optional['RuntimeTrustContext']) – Optional RuntimeTrustContext for trust propagation (CARE-015).
trust_verifier (Optional[Any]) – Optional TrustVerifier for trust verification (CARE-016).
trust_verification_mode (str) – Trust verification mode: - “disabled”: No trust verification (default for backward compatibility) - “permissive”: Log trust violations but allow execution - “enforcing”: Block execution on trust violations
audit_generator (Optional[Any]) – Optional RuntimeAuditGenerator for EATP-compliant audit trails (CARE-018).
audit_log_to_stdout (bool) – Whether to log audit events to stdout (default False).
**kwargs – Additional configuration (passed to mixins via super())
enable_resource_limits (bool)
- Raises:
ValueError – If configuration parameters are invalid
- Extracted From:
LocalRuntime.__init__ (lines 190-350 in local.py) - Configuration validation logic (lines 246-275) - Parameter initialization (lines 276-304) - Enterprise feature setup (lines 298-350)
- abstractmethod close() None[source]
Release runtime resources.
All runtime subclasses MUST implement proper cleanup. This ensures event loops, thread pools, connection pools, and other resources are released when the runtime is no longer needed.
- Usage:
>>> runtime = LocalRuntime() >>> try: ... results = runtime.execute(workflow) ... finally: ... runtime.close()
- Or use context manager (preferred):
>>> with LocalRuntime() as runtime: ... results = runtime.execute(workflow)
See also
acquire(): Increment reference count for shared runtimes
release(): Alias for close()
__enter__/__exit__: Context manager protocol
Added in: v0.12.0 (issue #71 — runtime lifecycle enforcement)
- Return type:
None
- __enter__() BaseRuntime[source]
Enter context manager.
- Return type:
- __exit__(exc_type, exc_val, exc_tb) None[source]
Exit context manager — calls close().
- Return type:
None
- abstractmethod execute(workflow: Workflow, *, soft_time_limit: float | None = None, time_limit: float | None = None, **kwargs)[source]
Execute workflow (runtime-specific implementation).
This method MUST be implemented by subclasses with appropriate signatures for sync/async execution:
Sync Implementation (LocalRuntime):
def execute( self, workflow: Workflow, parameters: Optional[Dict] = None, *, soft_time_limit: float | None = None, time_limit: float | None = None, **kwargs, ) -> Tuple[Dict[str, Any], str]: '''Execute workflow synchronously.''' # Implementation pass
Async Implementation (AsyncLocalRuntime):
async def execute( self, workflow: Workflow, parameters: Optional[Dict] = None, *, soft_time_limit: float | None = None, time_limit: float | None = None, **kwargs, ) -> Tuple[Dict[str, Any], str]: '''Execute workflow asynchronously.''' # Implementation pass
- Parameters:
workflow (Workflow) – The workflow to execute
soft_time_limit (float | None) – Optional advisory deadline in seconds. When reached, the running workflow is signalled via the cancellation token; user code MAY catch
SoftTimeLimitExceeded, finish in-flight work, and exit cleanly before the hard limit fires. Enforcement lands in #912 Shard 2.time_limit (float | None) – Optional unconditional kill deadline in seconds. When
time_limit + graceelapses, the wrapper raisesHardTimeLimitExceededregardless of acknowledgement. Enforcement lands in #912 Shard 2.**kwargs – Additional execution parameters (runtime-specific). Retained per the additive #912 Shard 1 contract; a future shard MAY tighten via a Rule 6a deprecation cycle.
- Returns:
Tuple of (results_dict, run_id)
- Raises:
RuntimeExecutionError – If execution fails
NotImplementedError – If called directly on BaseRuntime
- Implementation Requirements:
Generate run_id using self._generate_run_id()
Initialize metadata using self._initialize_execution_metadata()
Validate workflow before execution
Execute nodes in proper order
Collect and return results
Update metadata on completion/failure
Example Implementations:
- Sync (LocalRuntime):
>>> def execute(self, workflow, parameters=None, **kwargs): ... run_id = self._generate_run_id() ... metadata = self._initialize_execution_metadata(workflow, run_id) ... # Execute workflow synchronously ... results = self._execute_sync(workflow, parameters) ... return results, run_id
- Async (AsyncLocalRuntime):
>>> async def execute(self, workflow, parameters=None, **kwargs): ... run_id = self._generate_run_id() ... metadata = self._initialize_execution_metadata(workflow, run_id) ... # Execute workflow asynchronously ... results = await self._execute_async(workflow, parameters) ... return results, run_id
Trust Runtime
Trust-plane integration for runtime execution (EATP).
Trust context module for Kailash runtime (CARE-015, CARE-016, CARE-018).
This module provides RuntimeTrustContext for propagating trust information through workflow execution, TrustVerifier for bridging to Kaizen TrustOperations, and RuntimeAuditGenerator for EATP-compliant audit trails: - Human origin tracking across agent delegation - Constraint propagation and tightening - Audit trail for compliance - Bridge to Kaizen execution context - Trust verification for workflows, nodes, and resources - EATP-compliant audit generation
Usage:
from kailash.runtime.trust import (
RuntimeTrustContext,
TrustVerificationMode,
get_runtime_trust_context,
set_runtime_trust_context,
runtime_trust_context,
TrustVerifier,
TrustVerifierConfig,
VerificationResult,
MockTrustVerifier,
AuditEventType,
AuditEvent,
RuntimeAuditGenerator,
)
# Create and use trust context
ctx = RuntimeTrustContext(
trace_id="trace-123",
verification_mode=TrustVerificationMode.ENFORCING,
)
with runtime_trust_context(ctx):
runtime = LocalRuntime()
results, run_id = runtime.execute(workflow)
# Use trust verifier
verifier = TrustVerifier(
config=TrustVerifierConfig(mode="enforcing"),
)
result = await verifier.verify_workflow_access(
workflow_id="my-workflow",
agent_id="agent-123",
)
# Use audit generator
generator = RuntimeAuditGenerator(enabled=True)
await generator.workflow_started("run-1", "my-workflow", ctx)
- Version:
Added in: v0.11.0 Part of: CARE trust implementation (Phase 2)
- class kailash.runtime.trust.RuntimeTrustContext(trace_id: str = <factory>, human_origin: Any | None = None, delegation_chain: List[str] = <factory>, delegation_depth: int = 0, constraints: Dict[str, ~typing.Any]=<factory>, verification_mode: TrustVerificationMode = TrustVerificationMode.DISABLED, workflow_id: str | None = None, node_path: List[str] = <factory>, metadata: Dict[str, ~typing.Any]=<factory>, created_at: datetime = <factory>)[source]
Bases:
objectTrust context for workflow execution.
This dataclass captures trust information that propagates through workflow execution, enabling: - Human origin tracking across agent delegation chains - Constraint propagation with tightening semantics - Audit trail for compliance - Verification mode control
- Immutability:
with_node() and with_constraints() create new instances, preserving the original context unchanged.
- Variables:
trace_id (str) – Unique identifier for tracing execution (default: UUID)
human_origin (Any | None) – Origin information (compatible with Kaizen HumanOrigin)
delegation_chain (List[str]) – List of agent IDs in delegation chain
delegation_depth (int) – Current depth in delegation chain
constraints (Dict[str, Any]) – Dictionary of constraints (can only be tightened)
verification_mode (kailash.runtime.trust.context.TrustVerificationMode) – How to handle trust verification
workflow_id (str | None) – ID of the workflow being executed
node_path (List[str]) – Execution path through nodes (for audit)
metadata (Dict[str, Any]) – Additional metadata for extensibility
created_at (datetime.datetime) – When this context was created
- Parameters:
Example
>>> ctx = RuntimeTrustContext( ... trace_id="trace-123", ... verification_mode=TrustVerificationMode.ENFORCING, ... constraints={"max_tokens": 1000}, ... ) >>> node_ctx = ctx.with_node("process_data") >>> node_ctx.node_path ['process_data']
- verification_mode: TrustVerificationMode = 'disabled'
- with_node(node_id: str) RuntimeTrustContext[source]
Create new context with node_id appended to node_path.
This method creates a new RuntimeTrustContext instance with the given node_id added to the execution path. The original context remains unchanged (immutable pattern).
- Parameters:
node_id (str) – The node ID to append to the path
- Returns:
New RuntimeTrustContext with extended node_path
- Return type:
Example
>>> ctx = RuntimeTrustContext(node_path=["node1"]) >>> new_ctx = ctx.with_node("node2") >>> ctx.node_path ['node1'] >>> new_ctx.node_path ['node1', 'node2']
- with_constraints(additional_constraints: Dict[str, Any]) RuntimeTrustContext[source]
Create new context with tightened constraints.
This method creates a new RuntimeTrustContext instance with the additional constraints merged into the existing ones using tightening semantics. The original context remains unchanged (immutable pattern).
- Tightening rules:
Numeric values: takes the minimum (tighter limit)
Sets/lists: takes the intersection (fewer allowed items)
Booleans: once False (restricted), stays False
New keys: added freely (adding a constraint is tightening)
Strings: new value replaces old (caller’s responsibility)
- Parameters:
additional_constraints (Dict[str, Any]) – Constraints to merge/tighten
- Returns:
New RuntimeTrustContext with tightened constraints
- Return type:
Example
>>> ctx = RuntimeTrustContext(constraints={"max_tokens": 1000}) >>> new_ctx = ctx.with_constraints({"max_tokens": 500, "allowed_tools": ["read"]}) >>> new_ctx.constraints {'max_tokens': 500, 'allowed_tools': ['read']} >>> loosened = ctx.with_constraints({"max_tokens": 9999}) >>> loosened.constraints["max_tokens"] # Stays at 1000 (tighter) 1000
- to_dict() Dict[str, Any][source]
Serialize context to dictionary.
Handles: - datetime fields as ISO format strings - human_origin via its to_dict() method if available - TrustVerificationMode as string value
Example
>>> ctx = RuntimeTrustContext(trace_id="trace-123") >>> data = ctx.to_dict() >>> data["trace_id"] 'trace-123'
- classmethod from_dict(data: Dict[str, Any]) RuntimeTrustContext[source]
Deserialize context from dictionary.
Handles: - ISO format strings as datetime - String verification mode values
- Parameters:
data (Dict[str, Any]) – Dictionary representation of the context
- Returns:
RuntimeTrustContext instance
- Return type:
Example
>>> data = {"trace_id": "trace-123", ...} >>> ctx = RuntimeTrustContext.from_dict(data) >>> ctx.trace_id 'trace-123'
- classmethod from_kaizen_context(ctx: Any) RuntimeTrustContext[source]
Bridge from Kaizen ExecutionContext.
Creates a RuntimeTrustContext from a Kaizen ExecutionContext, mapping relevant fields and setting verification_mode to ENFORCING since Kaizen contexts imply trust enforcement.
This method handles the case where Kaizen is not installed gracefully, using duck typing to access expected attributes.
- Parameters:
ctx (Any) – Kaizen ExecutionContext (or any object with expected attributes)
- Returns:
RuntimeTrustContext bridged from Kaizen context
- Return type:
Example
>>> from kaizen.core.context import ExecutionContext >>> kaizen_ctx = ExecutionContext(...) >>> runtime_ctx = RuntimeTrustContext.from_kaizen_context(kaizen_ctx) >>> runtime_ctx.verification_mode TrustVerificationMode.ENFORCING
- __init__(trace_id: str = <factory>, human_origin: Any | None = None, delegation_chain: List[str] = <factory>, delegation_depth: int = 0, constraints: Dict[str, ~typing.Any]=<factory>, verification_mode: TrustVerificationMode = TrustVerificationMode.DISABLED, workflow_id: str | None = None, node_path: List[str] = <factory>, metadata: Dict[str, ~typing.Any]=<factory>, created_at: datetime = <factory>) None
- class kailash.runtime.trust.TrustVerificationMode(value)[source]
Bases:
EnumTrust verification modes for runtime execution.
- Modes:
DISABLED: No trust verification (default for backward compatibility) PERMISSIVE: Log trust violations but allow execution ENFORCING: Block execution on trust violations
- DISABLED = 'disabled'
- PERMISSIVE = 'permissive'
- ENFORCING = 'enforcing'
- classmethod __contains__(member)
Return True if member is a member of this enum raises TypeError if member is not an enum member
note: in 3.12 TypeError will no longer be raised, and True will also be returned if member is the value of a member in this enum
- classmethod __getitem__(name)
Return the member matching name.
- classmethod __iter__()
Return members in definition order.
- classmethod __len__()
Return the number of members (no aliases)
- kailash.runtime.trust.get_runtime_trust_context() RuntimeTrustContext | None[source]
Get the current runtime trust context.
Returns the RuntimeTrustContext from the current context variable, or None if no context is set.
- Returns:
Current RuntimeTrustContext or None
- Return type:
RuntimeTrustContext | None
Example
>>> ctx = get_runtime_trust_context() >>> if ctx: ... print(f"Trace: {ctx.trace_id}")
- kailash.runtime.trust.set_runtime_trust_context(ctx: RuntimeTrustContext | None) None[source]
Set the runtime trust context.
Sets the RuntimeTrustContext in the current context variable. Pass None to clear the context.
- Parameters:
ctx (RuntimeTrustContext | None) – RuntimeTrustContext to set, or None to clear
- Return type:
None
Example
>>> ctx = RuntimeTrustContext(trace_id="trace-123") >>> set_runtime_trust_context(ctx) >>> get_runtime_trust_context().trace_id 'trace-123'
- kailash.runtime.trust.runtime_trust_context(ctx: RuntimeTrustContext) Iterator[RuntimeTrustContext][source]
Context manager for scoped trust context propagation.
Sets the trust context for the duration of the block, then resets to the previous value on exit (even on exception).
- Parameters:
ctx (RuntimeTrustContext) – RuntimeTrustContext to use within the block
- Yields:
The same RuntimeTrustContext that was passed in
- Return type:
Example
>>> ctx = RuntimeTrustContext(trace_id="trace-123") >>> with runtime_trust_context(ctx) as active_ctx: ... assert get_runtime_trust_context() is ctx ... print(active_ctx.trace_id) trace-123
- class kailash.runtime.trust.VerificationResult(allowed: bool, reason: str | None = None, constraints: Dict[str, ~typing.Any]=<factory>, capability_used: str | None = None, trace_id: str | None = None)[source]
Bases:
objectResult of a trust verification check.
Represents the outcome of verifying whether an agent has permission to execute a workflow, node, or access a resource.
- Variables:
allowed (bool) – Whether the operation is allowed
reason (str | None) – Human-readable reason for the decision
constraints (Dict[str, Any]) – Any constraints that apply to the allowed operation
capability_used (str | None) – The capability that granted access (if any)
trace_id (str | None) – Trace ID for audit trail
- Parameters:
Example
>>> result = VerificationResult(allowed=True, reason="Access granted") >>> if result: # Uses __bool__ ... print("Allowed!") Allowed!
- __bool__() bool[source]
Allow using result directly in boolean context.
- Returns:
True if allowed, False otherwise
- Return type:
- class kailash.runtime.trust.TrustVerifierConfig(mode: str = 'disabled', cache_enabled: bool = True, cache_ttl_seconds: int = 60, fallback_allow: bool | None = None, audit_denials: bool = True, high_risk_nodes: List[str] = <factory>)[source]
Bases:
objectConfiguration for TrustVerifier.
- Variables:
mode (str) – Verification mode - “disabled”, “permissive”, or “enforcing”
cache_enabled (bool) – Whether to cache verification results
cache_ttl_seconds (int) – Time-to-live for cached results in seconds
fallback_allow (bool | None) – Whether to allow operations when verifier unavailable
audit_denials (bool) – Whether to log denied operations
high_risk_nodes (List[str]) – List of node types that require elevated verification
- Parameters:
Example
>>> config = TrustVerifierConfig( ... mode="enforcing", ... cache_ttl_seconds=120, ... high_risk_nodes=["BashCommand", "FileWrite"], ... )
- class kailash.runtime.trust.TrustVerifier(kaizen_backend: Any | None = None, config: TrustVerifierConfig | None = None)[source]
Bases:
objectBridge between Core SDK runtime and Kaizen TrustOperations for verification.
TrustVerifier provides a verification layer that can optionally integrate with Kaizen’s TrustOperations to enforce trust policies on workflow, node, and resource access.
- Modes:
DISABLED: No verification, all operations allowed (default)
PERMISSIVE: Verify and log, but allow even if denied
ENFORCING: Block operations that fail verification
- Caching:
Results are cached to avoid repeated backend calls. Cache entries expire after the configured TTL.
Example
>>> verifier = TrustVerifier( ... config=TrustVerifierConfig(mode="enforcing"), ... ) >>> result = await verifier.verify_workflow_access( ... workflow_id="my-workflow", ... agent_id="agent-123", ... ) >>> if result.allowed: ... print("Access granted")
- Parameters:
kaizen_backend (Optional[Any])
config (Optional[TrustVerifierConfig])
- __init__(kaizen_backend: Any | None = None, config: TrustVerifierConfig | None = None) None[source]
Initialize the TrustVerifier.
- Parameters:
kaizen_backend (Any | None) – Optional Kaizen TrustOperations instance for verification
config (TrustVerifierConfig | None) – Optional configuration (defaults to TrustVerifierConfig())
- Return type:
None
- property is_enforcing: bool
Check if verification is in enforcing mode.
- Returns:
True if mode is ENFORCING
- invalidate_agent(agent_id: str) int[source]
Invalidate all cached results for a specific agent (CARE-043).
Call this when an agent is revoked to ensure revocation takes immediate effect without waiting for cache TTL expiry.
- invalidate_node(node_type: str) int[source]
Invalidate all cached results for a specific node type (CARE-043).
- async verify_workflow_access(workflow_id: str, agent_id: str, trust_context: RuntimeTrustContext | None = None) VerificationResult[source]
Verify if an agent can execute a workflow.
- Parameters:
workflow_id (str) – The workflow to verify access to
agent_id (str) – The agent requesting access
trust_context (RuntimeTrustContext | None) – Optional RuntimeTrustContext for additional context
- Returns:
VerificationResult indicating whether access is allowed
- Return type:
- async verify_node_access(node_id: str, node_type: str, agent_id: str, trust_context: RuntimeTrustContext | None = None) VerificationResult[source]
Verify if an agent can execute a specific node.
High-risk nodes (configured in TrustVerifierConfig) receive elevated verification when a backend is available.
- Parameters:
node_id (str) – The node instance ID
node_type (str) – The node type (e.g., “BashCommand”, “HttpRequest”)
agent_id (str) – The agent requesting access
trust_context (RuntimeTrustContext | None) – Optional RuntimeTrustContext for additional context
- Returns:
VerificationResult indicating whether access is allowed
- Return type:
- async verify_resource_access(resource: str, action: str, agent_id: str, trust_context: RuntimeTrustContext | None = None) VerificationResult[source]
Verify if an agent can access a resource.
- Parameters:
resource (str) – The resource path or identifier
action (str) – The action to perform (e.g., “read”, “write”)
agent_id (str) – The agent requesting access
trust_context (RuntimeTrustContext | None) – Optional RuntimeTrustContext for additional context
- Returns:
VerificationResult indicating whether access is allowed
- Return type:
- class kailash.runtime.trust.MockTrustVerifier(default_allow: bool = True, denied_agents: List[str] | None = None, denied_nodes: List[str] | None = None, config: TrustVerifierConfig | None = None)[source]
Bases:
TrustVerifierMock verifier for testing without Kaizen backend.
Provides configurable allow/deny behavior for testing trust verification without requiring an actual Kaizen TrustOperations backend.
Example
>>> verifier = MockTrustVerifier( ... default_allow=True, ... denied_agents=["blocked-agent"], ... denied_nodes=["BashCommand"], ... ) >>> result = await verifier.verify_workflow_access( ... workflow_id="test-wf", ... agent_id="good-agent", ... ) >>> result.allowed True
- Parameters:
default_allow (bool)
denied_agents (Optional[List[str]])
denied_nodes (Optional[List[str]])
config (Optional[TrustVerifierConfig])
- __init__(default_allow: bool = True, denied_agents: List[str] | None = None, denied_nodes: List[str] | None = None, config: TrustVerifierConfig | None = None) None[source]
Initialize the MockTrustVerifier.
- Parameters:
default_allow (bool) – Default behavior when not explicitly denied
denied_agents (List[str] | None) – List of agent IDs that should be denied
denied_nodes (List[str] | None) – List of node types that should be denied
config (TrustVerifierConfig | None) – Optional configuration (defaults to enforcing mode)
- Return type:
None
- async verify_workflow_access(workflow_id: str, agent_id: str, trust_context: RuntimeTrustContext | None = None) VerificationResult[source]
Verify workflow access using mock rules.
- Parameters:
workflow_id (str) – The workflow to verify access to
agent_id (str) – The agent requesting access
trust_context (RuntimeTrustContext | None) – Optional RuntimeTrustContext for additional context
- Returns:
VerificationResult based on mock rules
- Return type:
- async verify_node_access(node_id: str, node_type: str, agent_id: str, trust_context: RuntimeTrustContext | None = None) VerificationResult[source]
Verify node access using mock rules.
- Parameters:
node_id (str) – The node instance ID
node_type (str) – The node type
agent_id (str) – The agent requesting access
trust_context (RuntimeTrustContext | None) – Optional RuntimeTrustContext for additional context
- Returns:
VerificationResult based on mock rules
- Return type:
- invalidate_agent(agent_id: str) int
Invalidate all cached results for a specific agent (CARE-043).
Call this when an agent is revoked to ensure revocation takes immediate effect without waiting for cache TTL expiry.
- invalidate_node(node_type: str) int
Invalidate all cached results for a specific node type (CARE-043).
- property is_enforcing: bool
Check if verification is in enforcing mode.
- Returns:
True if mode is ENFORCING
- async verify_resource_access(resource: str, action: str, agent_id: str, trust_context: RuntimeTrustContext | None = None) VerificationResult[source]
Verify resource access using mock rules.
- Parameters:
resource (str) – The resource path or identifier
action (str) – The action to perform
agent_id (str) – The agent requesting access
trust_context (RuntimeTrustContext | None) – Optional RuntimeTrustContext for additional context
- Returns:
VerificationResult based on mock rules
- Return type:
- class kailash.runtime.trust.AuditEventType(value)[source]
-
Well-known audit event types (cross-domain union).
String-backed enum whose
.valueis used to populate the canonicalAuditEvent.event_typefield. This enum provides a shared vocabulary for high-level trust-plane events; domain-specific modules MAY define their own enums as long as the string values are preserved inAuditEvent.event_type.- ACTION_EXECUTED = 'action_executed'
- ACTION_DENIED = 'action_denied'
- DELEGATION_CREATED = 'delegation_created'
- DELEGATION_REVOKED = 'delegation_revoked'
- TRUST_ESTABLISHED = 'trust_established'
- TRUST_REVOKED = 'trust_revoked'
- POLICY_CHANGED = 'policy_changed'
- ACCESS_GRANTED = 'access_granted'
- ACCESS_DENIED = 'access_denied'
- CONSTRAINT_VIOLATED = 'constraint_violated'
- SYSTEM_EVENT = 'system_event'
- CUSTOM = 'custom'
- WORKFLOW_START = 'workflow_start'
- WORKFLOW_END = 'workflow_end'
- WORKFLOW_ERROR = 'workflow_error'
- NODE_START = 'node_start'
- NODE_END = 'node_end'
- NODE_ERROR = 'node_error'
- TRUST_VERIFICATION = 'trust_verification'
- TRUST_DENIED = 'trust_denied'
- RESOURCE_ACCESS = 'resource_access'
- DELEGATION_USED = 'delegation_used'
- DISCLOSURE = 'disclosure'
- encode(encoding='utf-8', errors='strict')
Encode the string using the codec registered for encoding.
- encoding
The encoding in which to encode the string.
- errors
The error handling scheme to use for encoding errors. The default is ‘strict’ meaning that encoding errors raise a UnicodeEncodeError. Other possible values are ‘ignore’, ‘replace’ and ‘xmlcharrefreplace’ as well as any other name registered with codecs.register_error that can handle UnicodeEncodeErrors.
- replace(old, new, count=-1, /)
Return a copy with all occurrences of substring old replaced by new.
- count
Maximum number of occurrences to replace. -1 (the default value) means replace all occurrences.
If the optional argument count is given, only the first count occurrences are replaced.
- split(sep=None, maxsplit=-1)
Return a list of the substrings in the string, using sep as the separator string.
- sep
The separator used to split the string.
When set to None (the default value), will split on any whitespace character (including n r t f and spaces) and will discard empty strings from the result.
- maxsplit
Maximum number of splits. -1 (the default value) means no limit.
Splitting starts at the front of the string and works to the end.
Note, str.split() is mainly useful for data that has been intentionally delimited. With natural text that includes punctuation, consider using the regular expression module.
- rsplit(sep=None, maxsplit=-1)
Return a list of the substrings in the string, using sep as the separator string.
- sep
The separator used to split the string.
When set to None (the default value), will split on any whitespace character (including n r t f and spaces) and will discard empty strings from the result.
- maxsplit
Maximum number of splits. -1 (the default value) means no limit.
Splitting starts at the end of the string and works to the front.
- join(iterable, /)
Concatenate any number of strings.
The string whose method is called is inserted in between each given string. The result is returned as a new string.
Example: ‘.’.join([‘ab’, ‘pq’, ‘rs’]) -> ‘ab.pq.rs’
- capitalize()
Return a capitalized version of the string.
More specifically, make the first character have upper case and the rest lower case.
- casefold()
Return a version of the string suitable for caseless comparisons.
- title()
Return a version of the string where each word is titlecased.
More specifically, words start with uppercased characters and all remaining cased characters have lower case.
- center(width, fillchar=' ', /)
Return a centered string of length width.
Padding is done using the specified fill character (default is a space).
- count(sub[, start[, end]]) int
Return the number of non-overlapping occurrences of substring sub in string S[start:end]. Optional arguments start and end are interpreted as in slice notation.
- expandtabs(tabsize=8)
Return a copy where all tab characters are expanded using spaces.
If tabsize is not given, a tab size of 8 characters is assumed.
- find(sub[, start[, end]]) int
Return the lowest index in S where substring sub is found, such that sub is contained within S[start:end]. Optional arguments start and end are interpreted as in slice notation.
Return -1 on failure.
- partition(sep, /)
Partition the string into three parts using the given separator.
This will search for the separator in the string. If the separator is found, returns a 3-tuple containing the part before the separator, the separator itself, and the part after it.
If the separator is not found, returns a 3-tuple containing the original string and two empty strings.
- index(sub[, start[, end]]) int
Return the lowest index in S where substring sub is found, such that sub is contained within S[start:end]. Optional arguments start and end are interpreted as in slice notation.
Raises ValueError when the substring is not found.
- ljust(width, fillchar=' ', /)
Return a left-justified string of length width.
Padding is done using the specified fill character (default is a space).
- lower()
Return a copy of the string converted to lowercase.
- lstrip(chars=None, /)
Return a copy of the string with leading whitespace removed.
If chars is given and not None, remove characters in chars instead.
- rfind(sub[, start[, end]]) int
Return the highest index in S where substring sub is found, such that sub is contained within S[start:end]. Optional arguments start and end are interpreted as in slice notation.
Return -1 on failure.
- rindex(sub[, start[, end]]) int
Return the highest index in S where substring sub is found, such that sub is contained within S[start:end]. Optional arguments start and end are interpreted as in slice notation.
Raises ValueError when the substring is not found.
- rjust(width, fillchar=' ', /)
Return a right-justified string of length width.
Padding is done using the specified fill character (default is a space).
- rstrip(chars=None, /)
Return a copy of the string with trailing whitespace removed.
If chars is given and not None, remove characters in chars instead.
- rpartition(sep, /)
Partition the string into three parts using the given separator.
This will search for the separator in the string, starting at the end. If the separator is found, returns a 3-tuple containing the part before the separator, the separator itself, and the part after it.
If the separator is not found, returns a 3-tuple containing two empty strings and the original string.
- splitlines(keepends=False)
Return a list of the lines in the string, breaking at line boundaries.
Line breaks are not included in the resulting list unless keepends is given and true.
- strip(chars=None, /)
Return a copy of the string with leading and trailing whitespace removed.
If chars is given and not None, remove characters in chars instead.
- swapcase()
Convert uppercase characters to lowercase and lowercase characters to uppercase.
- translate(table, /)
Replace each character in the string using the given translation table.
- table
Translation table, which must be a mapping of Unicode ordinals to Unicode ordinals, strings, or None.
The table must implement lookup/indexing via __getitem__, for instance a dictionary or list. If this operation raises LookupError, the character is left untouched. Characters mapped to None are deleted.
- upper()
Return a copy of the string converted to uppercase.
- startswith(prefix[, start[, end]]) bool
Return True if S starts with the specified prefix, False otherwise. With optional start, test S beginning at that position. With optional end, stop comparing S at that position. prefix can also be a tuple of strings to try.
- endswith(suffix[, start[, end]]) bool
Return True if S ends with the specified suffix, False otherwise. With optional start, test S beginning at that position. With optional end, stop comparing S at that position. suffix can also be a tuple of strings to try.
- removeprefix(prefix, /)
Return a str with the given prefix string removed if present.
If the string starts with the prefix string, return string[len(prefix):]. Otherwise, return a copy of the original string.
- removesuffix(suffix, /)
Return a str with the given suffix string removed if present.
If the string ends with the suffix string and that suffix is not empty, return string[:-len(suffix)]. Otherwise, return a copy of the original string.
- isascii()
Return True if all characters in the string are ASCII, False otherwise.
ASCII characters have code points in the range U+0000-U+007F. Empty string is ASCII too.
- islower()
Return True if the string is a lowercase string, False otherwise.
A string is lowercase if all cased characters in the string are lowercase and there is at least one cased character in the string.
- isupper()
Return True if the string is an uppercase string, False otherwise.
A string is uppercase if all cased characters in the string are uppercase and there is at least one cased character in the string.
- istitle()
Return True if the string is a title-cased string, False otherwise.
In a title-cased string, upper- and title-case characters may only follow uncased characters and lowercase characters only cased ones.
- isspace()
Return True if the string is a whitespace string, False otherwise.
A string is whitespace if all characters in the string are whitespace and there is at least one character in the string.
- isdecimal()
Return True if the string is a decimal string, False otherwise.
A string is a decimal string if all characters in the string are decimal and there is at least one character in the string.
- isdigit()
Return True if the string is a digit string, False otherwise.
A string is a digit string if all characters in the string are digits and there is at least one character in the string.
- isnumeric()
Return True if the string is a numeric string, False otherwise.
A string is numeric if all characters in the string are numeric and there is at least one character in the string.
- isalpha()
Return True if the string is an alphabetic string, False otherwise.
A string is alphabetic if all characters in the string are alphabetic and there is at least one character in the string.
- isalnum()
Return True if the string is an alpha-numeric string, False otherwise.
A string is alpha-numeric if all characters in the string are alpha-numeric and there is at least one character in the string.
- isidentifier()
Return True if the string is a valid Python identifier, False otherwise.
Call keyword.iskeyword(s) to test whether string s is a reserved identifier, such as “def” or “class”.
- isprintable()
Return True if the string is printable, False otherwise.
A string is printable if all of its characters are considered printable in repr() or if it is empty.
- zfill(width, /)
Pad a numeric string with zeros on the left, to fill a field of the given width.
The string is never truncated.
- format(*args, **kwargs) str
Return a formatted version of S, using substitutions from args and kwargs. The substitutions are identified by braces (‘{’ and ‘}’).
- format_map(mapping) str
Return a formatted version of S, using substitutions from mapping. The substitutions are identified by braces (‘{’ and ‘}’).
- static maketrans()
Return a translation table usable for str.translate().
If there is only one argument, it must be a dictionary mapping Unicode ordinals (integers) or characters to Unicode ordinals, strings or None. Character keys will be then converted to ordinals. If there are two arguments, they must be strings of equal length, and in the resulting dictionary, each character in x will be mapped to the character at the same position in y. If there is a third argument, it must be a string, whose characters will be mapped to None in the result.
- __dir__()
Returns public methods and other interesting attributes.
- __init__(*args, **kwds)
- class kailash.runtime.trust.AuditEvent(event_id: str, timestamp: str, actor: str, action: str, resource: str, outcome: str, prev_hash: str, hash: str, parent_anchor_id: str | None = None, duration_ms: float | None = None, metadata: Dict[str, ~typing.Any]=<factory>, event_type: str | None = None, severity: str | None = None, description: str | None = None, user_id: str | None = None, tenant_id: str | None = None, resource_id: str | None = None, ip_address: str | None = None, user_agent: str | None = None, session_id: str | None = None, correlation_id: str | None = None, trace_id: str | None = None, workflow_id: str | None = None, node_id: str | None = None, agent_id: str | None = None, human_origin_id: str | None = None)[source]
Bases:
objectCanonical audit event – SPEC-08 single source of truth.
Consolidates every audit record type that previously existed in scattered modules (
nodes/admin/audit_log.AuditEvent,runtime/trust/audit.AuditEvent,trust/immutable_audit_log.AuditEntry,pact.audit.AuditAnchor). All of those are now either migrated to import from here or are deprecated-and-scheduled-for-deletion.The core fields (
event_id,timestamp,actor,action,resource,outcome,prev_hash,hash,parent_anchor_id,duration_ms,metadata) form the Merkle-chained audit trail. The extended fields are optional attributes for domain-specific consumers (workflow runtime, enterprise admin logging) and do not participate in the hash chain.- Variables:
event_id (str) – Unique identifier for this event.
timestamp (str) – UTC timestamp as ISO-8601 string for deterministic hashing.
actor (str) – Identifier of the entity that performed the action.
action (str) – Short description of what was done.
resource (str) – Identifier of the resource acted upon.
outcome (str) – Result of the action (
success,failure,denied,error).prev_hash (str) – SHA-256 hex digest of the previous event (or genesis sentinel).
hash (str) – SHA-256 hex digest covering all core fields above.
parent_anchor_id (str | None) – Link to triggering action for causal chains.
duration_ms (float | None) – Execution duration in milliseconds.
metadata (Dict[str, Any]) – Arbitrary additional context.
event_type (str | None) – High-level event category (see
AuditEventType).severity (str | None) – Severity level string (e.g.
"low","high","critical").description (str | None) – Human-readable event description.
user_id (str | None) – Authenticated user identifier (enterprise audit).
tenant_id (str | None) – Multi-tenant isolation key.
resource_id (str | None) – Structured resource identifier (distinct from
resource).ip_address (str | None) – Source IP address of the actor.
user_agent (str | None) – User-Agent string of the actor.
session_id (str | None) – Session correlation ID.
correlation_id (str | None) – Request/trace correlation ID.
trace_id (str | None) – Distributed trace ID for correlation across services.
workflow_id (str | None) – Workflow run identifier (runtime audit).
node_id (str | None) – Node instance identifier (runtime audit).
agent_id (str | None) – Agent identifier from delegation chain.
human_origin_id (str | None) – Identifier of the human at the root of the delegation chain.
- Parameters:
event_id (str)
timestamp (str)
actor (str)
action (str)
resource (str)
outcome (str)
prev_hash (str)
hash (str)
parent_anchor_id (str | None)
duration_ms (float | None)
event_type (str | None)
severity (str | None)
description (str | None)
user_id (str | None)
tenant_id (str | None)
resource_id (str | None)
ip_address (str | None)
user_agent (str | None)
session_id (str | None)
correlation_id (str | None)
trace_id (str | None)
workflow_id (str | None)
node_id (str | None)
agent_id (str | None)
human_origin_id (str | None)
- to_dict() Dict[str, Any][source]
Serialize to a plain dictionary.
All extended fields are included;
Nonevalues are preserved so round-trips are lossless.
- classmethod from_dict(data: Dict[str, Any]) AuditEvent[source]
Deserialize from a plain dictionary.
Unknown extra keys are ignored. Missing optional keys default to
Noneor the field default.- Parameters:
- Return type:
- verify_integrity() bool[source]
Verify this event’s hash matches its content.
Uses
hmac.compare_digestto prevent timing side-channels. Only the core Merkle-chained fields participate in the hash; extended domain fields are excluded so pre-SPEC-08 chains remain verifiable after migration.- Returns:
True if the hash is valid.
- Return type:
- __init__(event_id: str, timestamp: str, actor: str, action: str, resource: str, outcome: str, prev_hash: str, hash: str, parent_anchor_id: str | None = None, duration_ms: float | None = None, metadata: Dict[str, ~typing.Any]=<factory>, event_type: str | None = None, severity: str | None = None, description: str | None = None, user_id: str | None = None, tenant_id: str | None = None, resource_id: str | None = None, ip_address: str | None = None, user_agent: str | None = None, session_id: str | None = None, correlation_id: str | None = None, trace_id: str | None = None, workflow_id: str | None = None, node_id: str | None = None, agent_id: str | None = None, human_origin_id: str | None = None) None
- Parameters:
event_id (str)
timestamp (str)
actor (str)
action (str)
resource (str)
outcome (str)
prev_hash (str)
hash (str)
parent_anchor_id (str | None)
duration_ms (float | None)
event_type (str | None)
severity (str | None)
description (str | None)
user_id (str | None)
tenant_id (str | None)
resource_id (str | None)
ip_address (str | None)
user_agent (str | None)
session_id (str | None)
correlation_id (str | None)
trace_id (str | None)
workflow_id (str | None)
node_id (str | None)
agent_id (str | None)
human_origin_id (str | None)
- Return type:
None
- class kailash.runtime.trust.RuntimeAuditGenerator(audit_store: Any | None = None, enabled: bool = True, log_to_stdout: bool = False)[source]
Bases:
objectAudit trail generator for runtime workflow execution.
Generates EATP-compliant audit events for workflow and node execution, with optional persistence to Kaizen AuditStore.
- Features:
In-memory event storage for queries
Optional Kaizen AuditStore integration
Optional stdout logging
Extraction of trust context fields
Example
>>> generator = RuntimeAuditGenerator(enabled=True) >>> trust_ctx = RuntimeTrustContext(trace_id="trace-123") >>> await generator.workflow_started("run-1", "wf-1", trust_ctx) >>> events = generator.get_events()
- __init__(audit_store: Any | None = None, enabled: bool = True, log_to_stdout: bool = False) None[source]
Initialize the RuntimeAuditGenerator.
- async workflow_started(run_id: str, workflow_name: str, trust_context: 'RuntimeTrustContext' | None) AuditEvent[source]
Record workflow start event.
- Parameters:
- Returns:
The created AuditEvent
- Return type:
- async workflow_completed(run_id: str, duration_ms: int, trust_context: 'RuntimeTrustContext' | None) AuditEvent[source]
Record workflow completion event.
- Parameters:
- Returns:
The created AuditEvent
- Return type:
- async workflow_failed(run_id: str, error: str, duration_ms: int, trust_context: 'RuntimeTrustContext' | None) AuditEvent[source]
Record workflow failure event.
- Parameters:
- Returns:
The created AuditEvent
- Return type:
- async node_executed(run_id: str, node_id: str, node_type: str, duration_ms: int, trust_context: 'RuntimeTrustContext' | None) AuditEvent[source]
Record successful node execution event.
- Parameters:
- Returns:
The created AuditEvent
- Return type:
- async node_failed(run_id: str, node_id: str, node_type: str, error: str, duration_ms: int, trust_context: 'RuntimeTrustContext' | None) AuditEvent[source]
Record node failure event.
- Parameters:
run_id (str) – Unique run identifier
node_id (str) – Node instance ID
node_type (str) – Type of node that failed
error (str) – Error message or description
duration_ms (int) – Execution duration in milliseconds
trust_context (Optional['RuntimeTrustContext']) – RuntimeTrustContext for trust information
- Returns:
The created AuditEvent
- Return type:
- async trust_verification_performed(run_id: str, target: str, allowed: bool, reason: str, trust_context: 'RuntimeTrustContext' | None) AuditEvent[source]
Record trust verification event.
- Parameters:
run_id (str) – Unique run identifier
target (str) – What was verified (e.g., “workflow:name” or “node:type:id”)
allowed (bool) – Whether the verification allowed the operation
reason (str) – Reason for the verification result
trust_context (Optional['RuntimeTrustContext']) – RuntimeTrustContext for trust information
- Returns:
The created AuditEvent
- Return type:
- async resource_accessed(run_id: str, resource: str, action: str, result: str, trust_context: 'RuntimeTrustContext' | None) AuditEvent[source]
Record resource access event.
- Parameters:
- Returns:
The created AuditEvent
- Return type:
- get_events() List[AuditEvent][source]
Get all recorded events.
- Returns:
List of all AuditEvents in chronological order
- Return type:
- get_events_by_type(event_type: AuditEventType) List[AuditEvent][source]
Get events filtered by type.
- Parameters:
event_type (AuditEventType) – Type of events to retrieve
- Returns:
List of matching AuditEvents
- Return type:
Durable Execution & Distributed Dispatch (v2.14.0+)
The durable execution module provides per-node checkpoint persistence so workflows can resume from the last completed node after process restart, crash, or scheduled re-run. Both LocalRuntime and AsyncLocalRuntime route node completions through runtime.on_node_complete when an ExecutionTracker is configured.
Durable execution primitives for LocalRuntime / AsyncLocalRuntime.
This module provides the building blocks that wire per-node checkpoint
emission and history-event subscription into the runtime’s hot path
(_execute_workflow_with_tracking at local.py:2505):
NodeCompletionEvent— the canonical per-node event. Subscribers registered viaruntime.on_node_complete(callback)receive one of these per node completion.WorkflowShapeDriftError— raised when a caller resumes with anidempotency_keywhose persisted checkpoint was captured against a different workflow fingerprint. Same structural-confirmation pattern asgit reset --hard/force_drop/force_downgrade: refuse by default, requireforce_resume_with_drift=Trueto override.NodeCompletionHookRegistry— lightweight subscriber registry used by both LocalRuntime and AsyncLocalRuntime. Multi-subscriber dispatch supported; sync and async callbacks are both honored.compute_workflow_fingerprint()— deterministic SHA-256 over the workflow’s node IDs + edges + node types, used to detect shape drift between a saved checkpoint and a resume call.build_checkpoint_key()— deterministic hash of(workflow.fingerprint, idempotency_key, parameters)per architecture plan §6 risk register. Stable across processes; safe as a primary key.redact_event_for_persistence()— shared classification-aware redaction helper. Both this module’s checkpoint persistence path AND the W2 history store path MUST route through this helper before persisting any payload that may carry classified PKs or field names. Perrules/event-payload-classification.mdMUST Rules 1–3 and the cross-cutting invariant 3.1 inworkspaces/runtime-integration-trio/01-analysis/04-cross-cutting-architecture.md.
Per rules/specs-authority.md Rule 5 the spec describing this surface
(specs/core-runtime.md) lands AFTER the code.
- class kailash.runtime.durable.NodeCompletionEvent(run_id: str | None, workflow_id: str, workflow_fingerprint: str, node_id: str, node_type: str, outputs: ~typing.Mapping[str, ~typing.Any], started_at: ~datetime.datetime, ended_at: ~datetime.datetime, duration_ms: int, tenant_id: str | None = None, idempotency_key: str | None = None, error: str | None = None, metadata: ~typing.Mapping[str, ~typing.Any] = <factory>)[source]
Bases:
objectA per-node completion event emitted by the runtime hot path.
This is the canonical event shape that W2 (history store) and any other subscriber (metrics, audit, replication) consume. The event is frozen so subscribers cannot accidentally mutate it across handlers.
- Variables:
run_id (str | None) – The runtime-assigned execution ID.
Noneonly when the runtime ran without atask_managerAND the AsyncLocalRuntime path skipped the wall-clock-derived ID — which is rare and always recoverable fromidempotency_keyif present.workflow_id (str) – The workflow’s stable ID (
Workflow.workflow_id).workflow_fingerprint (str) – SHA-256 hex digest of the workflow’s structural shape (node IDs + edges + node types). Same fingerprint is used by
build_checkpoint_key(). Seecompute_workflow_fingerprint().node_id (str) – The completed node’s ID.
node_type (str) – The node’s class name (
node_instance.__class__.__name__).outputs (Mapping[str, Any]) – The node’s output dict (already classification-redacted by the persistence path before being handed to subscribers — see
redact_event_for_persistence()). Subscribers MUST NOT assume the raw output is present.started_at (datetime.datetime) – UTC timestamp when the node started executing.
ended_at (datetime.datetime) – UTC timestamp when the node completed (success or recorded error).
duration_ms (int) –
(ended_at - started_at)in milliseconds, rounded to int.tenant_id (str | None) – Tenant scope from the runtime context.
Nonewhen the runtime is single-tenant. History/checkpoint rows MUST partition on this perrules/tenant-isolation.mdMUST Rule 5.idempotency_key (str | None) – The caller-supplied resume key, if any.
error (str | None) –
Noneon success; a string repr of the exception on failure. Subscribers see the event whether the node succeeded or failed, because both states are part of the durable history.metadata (Mapping[str, Any]) – Free-form dict for cross-cutting context (correlation IDs, agent IDs, classification policy snapshot). Already redacted.
- Parameters:
- classmethod from_dict(data: Mapping[str, Any]) NodeCompletionEvent[source]
Reconstruct from a dict produced by
to_dict().- Parameters:
- Return type:
- __init__(run_id: str | None, workflow_id: str, workflow_fingerprint: str, node_id: str, node_type: str, outputs: ~typing.Mapping[str, ~typing.Any], started_at: ~datetime.datetime, ended_at: ~datetime.datetime, duration_ms: int, tenant_id: str | None = None, idempotency_key: str | None = None, error: str | None = None, metadata: ~typing.Mapping[str, ~typing.Any] = <factory>) None
- Parameters:
- Return type:
None
- class kailash.runtime.durable.NodeCompletionHookRegistry[source]
Bases:
objectRegistry of per-node-completion subscribers.
Used by both LocalRuntime and AsyncLocalRuntime to provide
runtime.on_node_complete(callback). Multi-subscriber dispatch is supported (W2 history store, metrics, audit). Both sync and async callbacks are honored — the runtime callsdispatch_async()from inside the per-node async path.The registry is intentionally thread-safe-without-locks for the common case (subscribers register once at runtime construction and are never mutated mid-run). Concurrent
register()calls during a live run are not the design target; if a future use case needs them, add anasyncio.Lockhere — the existing tests will surface the race.- register(callback: Callable[[NodeCompletionEvent], None | Awaitable[None]]) Callable[[], None][source]
Register
callback. Returns an unregister function.- Parameters:
callback (Callable[[NodeCompletionEvent], None | Awaitable[None]])
- Return type:
Callable[[], None]
- async dispatch_async(event: NodeCompletionEvent) None[source]
Dispatch
eventto all subscribers, awaiting any coroutines.Subscriber exceptions are logged at WARN (per
rules/observability.mdRule 7 — partial failure across subscribers MUST emit a WARN line) and do NOT abort the runtime; a misbehaving metrics subscriber MUST NOT take down the workflow execution that the user cares about. Failed subscribers are counted and surfaced in the WARN log.Per
rules/zero-tolerance.mdRule 3 we still raiseCancelledError/KeyboardInterrupt/SystemExit.- Parameters:
event (NodeCompletionEvent)
- Return type:
None
- exception kailash.runtime.durable.WorkflowShapeDriftError(idempotency_key: str, stored_fingerprint: str, current_fingerprint: str)[source]
Bases:
RuntimeErrorRaised when an idempotency-key resume targets a different workflow.
The runtime persisted a checkpoint under
idempotency_key=Kfor a workflow whose structural fingerprint wasF1. The caller is now resuming under the sameidempotency_key=Kbut supplying a workflow whose fingerprint isF2 != F1. The completed-node outputs in the checkpoint were produced by the OLD shape — replaying them under the new shape can corrupt downstream state and is the “fake transaction” failure mode class flagged byrules/zero-tolerance.mdRule 2.The default disposition is to refuse. Callers who explicitly want to discard the prior checkpoint and start fresh MUST pass
force_resume_with_drift=True. Same structural-confirmation pattern asgit reset --hard(must verify clean tree) andMigrationManager.apply_downgrade(must passforce_downgrade=True).- Parameters:
- Return type:
None
- add_note()
Exception.add_note(note) – add a note to the exception
- args
- with_traceback()
Exception.with_traceback(tb) – set self.__traceback__ to tb and return self.
- kailash.runtime.durable.compute_workflow_fingerprint(workflow: Any) str[source]
Deterministic SHA-256 over a workflow’s structural shape.
The fingerprint covers:
sorted node IDs
sorted (source, target) edge tuples
each node’s class name (the type, not the config — config drift is handled by the parameters channel of
build_checkpoint_key())
Node config / parameter values are NOT included — those drift via the
parametersargument ofbuild_checkpoint_key(). Including config here would make every parameter override invalidate every checkpoint, defeating the purpose of resume.Returns the hex digest as a string.
- kailash.runtime.durable.build_checkpoint_key(workflow_fingerprint: str, idempotency_key: str, parameters: Mapping[str, Any] | None = None, *, tenant_id: str | None = None) str[source]
Build a stable checkpoint key.
The key is the SHA-256 hex digest of
(tenant_id, workflow_fingerprint, idempotency_key, parameters). Stable across processes — two calls with the same inputs produce the same key. Per architecture plan §6 risk register.The
tenant_idpartition is mandatory perrules/tenant-isolation.mdMUST Rule 5: a tenant MUST NOT be able to read another tenant’s checkpoint by guessing or replaying the sameidempotency_key.Parameters that are not JSON-serialisable degrade gracefully via
default=str— the key remains stable for stable inputs.
- kailash.runtime.durable.redact_event_for_persistence(event: NodeCompletionEvent, *, classification_policy: Any | None = None) NodeCompletionEvent[source]
Return a copy of
eventsafe to persist to checkpoint / history.This is the shared redaction helper mandated by the cross-cutting invariant 3.1 in the architecture plan. Both
LocalRuntimecheckpoint emission AND the W2 history store path MUST route every event through this function before anystore.save(...)/store.record(...)call.Behavior:
If
classification_policyisNone(no classification configured), the event is returned unchanged. This matches the runtime’s default single-tenant single-classification posture.If
classification_policyis provided, this function consults the policy for each field ofevent.outputsAT EVERY DEPTH (recursive walk through nestedMappingandSequencevalues) and:drops any field tagged
REDACTand replaces it with the sentinel"[REDACTED]"(NOTNone—Noneis a valid unredacted value),hashes any field tagged
HASH_PKviaformat_record_id_for_event(a stable SHA-256-based digest),leaves classification-free fields untouched.
Recursive walk semantics (W6 nested-redaction fix):
The policy is consulted with
field_pathjoined by.separators, e.g."customer.ssn"for a top-levelcustomerdict containingssn, or"items.0.password"for the first element of a top-levelitemslist whose dict carriespassword.If the policy returns
REDACT/HASH_PKfor a non-leaf (a Mapping or Sequence at the path), the entire subtree is replaced with the sentinel — same wrapper-level semantics as before W6 for callers that tag the outer field.Strings and bytes are leaves even though they are Sequences; iterating their characters/octets is never a redaction goal.
Recursion is fail-closed at every depth: a policy raise mid- recursion routes that node to
REDACTexactly like the top-level path used to.
The function NEVER raises on a missing policy method — when the policy doesn’t expose the expected duck-typed surface, it returns the event unchanged with a single DEBUG log line. Per
rules/observability.mdRule 8 the schema-revealing field names (e.g.users.ssn) ride at DEBUG, not WARN/INFO.The classified-field-name partition (per
rules/event-payload-classification.mdMUST Rule 3) lives in the event’smetadatadict undermetadata["classification_summary"]and contains:unclassified_fields: list[str] — names safe to logclassified_field_count: int — count of redacted+hashed fields
The classified field NAMES themselves are intentionally NOT in the summary — only the count is, so an operator-facing log line at INFO can report “3 classified fields redacted” without leaking the schema.
The output is a NEW
NodeCompletionEvent(frozen dataclass) — the caller’s original event is never mutated.- Parameters:
event (NodeCompletionEvent)
classification_policy (Any | None)
- Return type:
- kailash.runtime.durable.redacted_tracker_state_for_checkpoint(tracker_state: Mapping[str, Any], *, classification_policy: Any | None = None, workflow_id: str = '', workflow_fingerprint: str = '', tenant_id: str | None = None, idempotency_key: str | None = None) Dict[str, Any][source]
Return a copy of
tracker_statewith per-node outputs redacted.The W2
runtime.on_node_completehook contract — “no subscriber ever observes a classified PK or a redacted field’s raw value” (LocalRuntime.on_node_complete()docstring) — extends to EVERY persistence surface that handles per-node outputs. The checkpoint write-path is one such surface:encode_checkpoint_payloadwas previously called withtracker_state=execution_tracker.to_dict(), which embeds raw classified outputs innode_outputs[<node_id>].Anyone using
LocalRuntime(checkpoint_store=…, checkpoint_after_each_node=True)with aclassification_policywas silently writing raw classified field values to disk — exactly the “fake redaction / fake classification” failure mode named byrules/zero-tolerance.mdRule 2 and the “every persistence surface that handles classified fields routes through redact_event_for_persistence semantics” invariant established by W6.Strategy: walk
tracker_state["node_outputs"]and, for each node’s cached outputs dict, build a syntheticNodeCompletionEventwhoseoutputscarry the cached payload, runredact_event_for_persistence()over it, and write the redacted outputs back into a fresh tracker-state dict. Other tracker fields (completed_nodesordering) are copied verbatim — they carry no classified content.Behavior matches
redact_event_for_persistence()exactly: whenclassification_policyisNonethe function returns a deep copy oftracker_stateunchanged. When a policy is provided, every classified field in every per-node output dict is replaced with the"[REDACTED]"sentinel or hashed viaHASH_PKsemantics.The function NEVER raises on a missing
node_outputskey — the tracker shape is treated permissively so downstream callers (test fixtures, alternative trackers) can skip the field if they have no per-node outputs to record.- Parameters:
tracker_state (Mapping[str, Any]) – The dict produced by
ExecutionTracker.to_dict()(or any compatible structure withnode_outputskeyed by node_id).classification_policy (Any | None) – Same policy object passed to
redact_event_for_persistence(). WhenNone, returns a deep copy unchanged (no-op redaction = back-compat).workflow_id (str) – Context propagated into the synthetic events so a future policy implementation that consults event-level context (per the architecture plan §3.1 invariant) sees the right scope.
workflow_fingerprint (str) – Context propagated into the synthetic events so a future policy implementation that consults event-level context (per the architecture plan §3.1 invariant) sees the right scope.
tenant_id (str | None) – Context propagated into the synthetic events so a future policy implementation that consults event-level context (per the architecture plan §3.1 invariant) sees the right scope.
idempotency_key (str | None) – Context propagated into the synthetic events so a future policy implementation that consults event-level context (per the architecture plan §3.1 invariant) sees the right scope.
- Return type:
Notes
Known constraints. Synthetic events constructed by this helper use
datetime.now(timezone.utc)as bothstarted_atandended_atbecause the per-node tracker state captured byExecutionTrackerdoes not preserve those wall-clock fields. Policies that consult time-bounded classification rules (e.g. “redact incidents younger than 30 days”) will see the redaction-execution time at this surface, NOT the original event timestamps. Such policies MUST use the realruntime.on_node_completehook surface where the runtime supplies actualstarted_at/ended_atper node. The checkpoint write-path is a persistence-redaction surface only; time-windowed policy decisions belong on the hook surface.- Returns:
A NEW dict. The input
tracker_stateis never mutated; adeep-copy of
node_outputsis taken so the caller’s tracker issafe.
- Parameters:
- Return type:
- kailash.runtime.durable.encode_checkpoint_payload(*, workflow_fingerprint: str, tracker_state: Mapping[str, Any], tenant_id: str | None = None, workflow_id: str | None = None, idempotency_key: str | None = None) bytes[source]
Encode the checkpoint blob with a header that includes the fingerprint.
The header is what
decode_checkpoint_payload()reads to detect shape drift before re-using cached node outputs. UTF-8 JSON; deflated only by the underlying store if it chooses to.
- kailash.runtime.durable.decode_checkpoint_payload(blob: bytes) Dict[str, Any][source]
Decode a blob produced by
encode_checkpoint_payload().Raises
ValueErrorif the header is missing required fields. This is the runtime-layer typed-error gate perrules/zero-tolerance.mdRule 3a — opaqueKeyErrorfrompayload["workflow_fingerprint"]is BLOCKED.
- kailash.runtime.durable.check_shape_drift_or_raise(*, idempotency_key: str, stored_payload: Mapping[str, Any], current_fingerprint: str, force_resume_with_drift: bool) None[source]
Compare stored vs current fingerprint; raise on drift.
See
WorkflowShapeDriftErrorfor semantics.
- kailash.runtime.durable.resolve_tenant_id(runtime: Any) str | None[source]
Resolve the active tenant_id from the runtime + ContextVar.
Per the cross-cutting invariant 3.3, both checkpoint rows and history rows MUST carry the active tenant. We consult, in order:
runtime.user_context.tenant_idif exposedkailash.trust.auth.context.get_current_tenant_id()if importableNone(single-tenant deployments)
A missing trust subsystem (ImportError / AttributeError on the
kailash.trust.auth.contextsurface) is a valid single-tenant deployment posture and is logged WARN once per process for operator visibility. ANY other exception (ContextVar lookup error, runtime bug, propagation glitch) is RE-RAISED — silently swallowing them would mask cross-tenant exposure under a buggy trust subsystem. Same posture asrules/zero-tolerance.mdRule 3 (no silent fallbacks): narrow the except clause to the documented “optional surface” path; let everything else propagate.
- class kailash.runtime.durable.DurableExecutionEngine(*, checkpoint_store: Any | None, history_store: Any | None, dispatcher: Any | None, idempotency_key_default: str | None, runtime_factory: Callable[[...], Any], runtime_kwargs: Mapping[str, Any] | None, execution_mode: Literal['in_process_only', 'dispatch_only', 'both'])[source]
Bases:
objectFirst-party durable execution engine for Kailash workflows.
Composes the runtime-integration-trio primitives — per-node checkpointing (W1), persistent workflow history (W2), and pluggable task dispatch (W3) — into a single facade callers can construct via
builder(). Each primitive is opt-in; an engine constructed with no primitives behaves like a plainAsyncLocalRuntime.Example:
from kailash.runtime.durable import DurableExecutionEngine from kailash.infrastructure.checkpoint_store import DBCheckpointStore from kailash.infrastructure.history_store import PostgresHistoryStore from kailash.infrastructure.task_queue import ( SQLTaskQueue, SQLTaskQueueDispatcher, ) engine = ( DurableExecutionEngine.builder() .checkpoint_store(DBCheckpointStore(conn)) .history_store(PostgresHistoryStore(conn)) .dispatch_via(SQLTaskQueueDispatcher(queue=SQLTaskQueue(conn))) .build() ) results, run_id = await engine.execute( workflow.build(), idempotency_key="user-42-prewarm", ) # Native history-store API for queries (tenant_id required): runs = await engine.history.list_runs(filter={"tenant_id": "default"}) events = await engine.history.get_run_events(run_id, tenant_id="default")
Composition contract
checkpoint_storeis forwarded toAsyncLocalRuntime(checkpoint_store=..., checkpoint_after_each_node=True)so per-node blobs land via the W1 hot path (seekailash.runtime.local_record_node_completion).history_storeis forwarded toAsyncLocalRuntime(history_store=...)which auto-subscribeshistory_store.record_eventagainst the hook registry at construction time. The engine does NOT callruntime.on_node_complete(history_store.record_event)separately — doing so would double-register the subscriber.dispatcheris consulted only when configured ANDexecution_modeselects a dispatching path ("both"or"dispatch_only"). When set under"both",execute()enqueues a fire-timeTaskBEFORE running the workflow in-process; the enqueue and the in-process run race, and the structural defense is layered (dispatcher PRIMARY KEY idempotency ontask_idprevents duplicate enqueue; W1 checkpoint resume short-circuits the second runner). Callers who need to eliminate the race entirely setexecution_mode="in_process_only"(skip enqueue) or"dispatch_only"(skip the in-process runtime call). Seeexecute()Routing notes (issue #882).
Immutability
The engine is immutable after
DurableExecutionEngineBuilder.build(). Mutating any of its primitives in-place after construction is unsupported — construct a new engine via the builder if the composition needs to change.- __init__(*, checkpoint_store: Any | None, history_store: Any | None, dispatcher: Any | None, idempotency_key_default: str | None, runtime_factory: Callable[[...], Any], runtime_kwargs: Mapping[str, Any] | None, execution_mode: Literal['in_process_only', 'dispatch_only', 'both']) None[source]
Construct an engine. Use
builder()for the public path.
- property runtime: Any
The wrapped
AsyncLocalRuntime(or factory-supplied alt).Returned for advanced callers who need direct access to the runtime — e.g. to register additional
on_node_completesubscribers beyond the W2 history store auto-subscribe. The runtime instance is the SAME oneengine.executedelegates to.
- property history: Any
The composed history store, if configured.
Noneotherwise.Exposes the native
WorkflowHistoryStoreread API (list_runs,get_run,get_run_events,list_failed). Tenant scope is mandatory on every read perrules/tenant-isolation.mdMUST Rule 5.
- property idempotency_key_default: str | None
The default idempotency key applied when
executeomits it.
- property execution_mode: Literal['in_process_only', 'dispatch_only', 'both']
The resolved execution mode for
execute()calls.One of
"in_process_only","dispatch_only", or"both". Set explicitly viaDurableExecutionEngineBuilder.execution_mode(), or auto-detected at build time ("both"when a dispatcher is configured,"in_process_only"otherwise).
- classmethod builder() DurableExecutionEngineBuilder[source]
Return a fresh fluent builder. See
DurableExecutionEngineBuilder.- Return type:
- async execute(workflow: Any, *, idempotency_key: str | None = None, inputs: Mapping[str, Any] | None = None, force_resume_with_drift: bool = False, dispatch_kwargs: Mapping[str, Any] | None = None, queue_name: str = 'default', soft_time_limit: float | None = None, time_limit: float | None = None, **kwargs: Any) Tuple[Dict[str, Any], str][source]
Execute workflow through the wrapped runtime and (optionally) dispatch.
- Parameters:
workflow (Any) – The workflow returned by
WorkflowBuilder.build().idempotency_key (str | None) – Caller-supplied resume key. Falls back to
idempotency_key_defaultwhen omitted. Forwarded toruntime.execute_workflow_async(idempotency_key=...)so the W1 checkpoint-resume path fires when a prior blob exists.inputs (Mapping[str, Any] | None) – Workflow inputs dict. Defaults to an empty dict.
force_resume_with_drift (bool) – Forwarded to the runtime — when
TrueanWorkflowShapeDriftErroris suppressed and the engine proceeds against the new shape per W1 §4.6.4. The default is to refuse on drift, matchinggit reset --keepandMigrationManager.apply_downgrade(force_downgrade=True).dispatch_kwargs (Mapping[str, Any] | None) – Free-form dict serialised into the dispatched
Taskkwargsfield. Ignored when no dispatcher is configured.queue_name (str) – Target queue when a dispatcher is configured. Defaults to
"default".soft_time_limit (float | None) – Optional advisory deadline in seconds (#912). Forwarded to the wrapped runtime. Raises
SoftTimeLimitExceededwhen reached; user code MAY catch and exit cleanly before the hard kill fires.time_limit (float | None) – Optional unconditional kill deadline in seconds (#912). Forwarded to the wrapped runtime. Raises
HardTimeLimitExceededaftertime_limit + graceregardless of soft-limit acknowledgement.kwargs (Any)
- Returns:
(results, run_id)from the wrapped runtime. The runtime’srun_idis also used to derive the dispatched task’sschedule_idso an operator can correlate the queue row with the history row.- Return type:
Notes
Dispatcher failures emit a WARN log via the standard runtime logger AND re-raise — the dispatch is part of the run’s durability contract per the W3 wave, not best-effort. Callers who want best-effort dispatch should construct a custom
Dispatcherwhoseenqueuecatches its own failures.Routing — execution_mode contract (issue #882)
The branch taken is determined by
execution_mode:"in_process_only"— runs in-process; skips enqueue even if a dispatcher is configured. Returns(results, run_id)from the wrapped runtime."dispatch_only"— enqueues only; the wrapped runtime is NOT invoked. Returns({}, schedule_id)so callers can correlate downstream worker output viaengine.history.get_run(schedule_id, ...). The emptyresultsdict is the explicit “no in-process completion” sentinel."both"— enqueues AND runs in-process. The two paths race; structural defenses below contain the blast radius.
When
"both"is configured, two actors (the in-process engine and a worker polling the queue) hold claims to the same task at once. The structural defenses are layered:Dispatcher
task_idPRIMARY KEY idempotency (DispatcherMUST Rule 1) prevents duplicate ENQUEUE — but does NOT prevent two different runners from each executing the same enqueued task once.W1 checkpoint resume short-circuits the second runner: when the in-process path completes a node and emits a checkpoint under the same
idempotency_key, the worker’s subsequentruntime.execute_workflow_async(idempotency_key=...)resolves to the checkpoint and skips the already-completed nodes.Race window: between enqueue and the in-process path emitting its first checkpoint, a worker that picks up the task can start executing nodes that the in-process path will then re-execute. The in-tree
SQLTaskQueueDispatcherworker + W1 checkpoint resume contain this; custom Dispatchers MUST honor the sameidempotency_keyresume contract or callers MUST switch to"in_process_only"/"dispatch_only"to eliminate the race entirely.
- class kailash.runtime.durable.DurableExecutionEngineBuilder[source]
Bases:
objectFluent builder for
DurableExecutionEngine.Build pattern:
DurableExecutionEngine.builder()returns a fresh builder.Chain optional
.checkpoint_store(store)/.history_store(store)/.dispatch_via(dispatcher)/.idempotency_key_default(key)/.runtime(factory)/.runtime_kwargs(mapping)calls.Call
.build()to produce the immutable engine.
Each setter returns
selfso the chain can run in one expression. Calling a setter twice OVERRIDES the prior value (no implicit fan-in) so the final.build()reflects the last setter call. The default runtime factory isAsyncLocalRuntime— pass a custom factory only when a subclass is needed (Docker, custom timeout, alternative scheduler).- checkpoint_store(store: Any) DurableExecutionEngineBuilder[source]
Configure the checkpoint store.
Noneclears the prior value.- Parameters:
store (Any)
- Return type:
- history_store(store: Any) DurableExecutionEngineBuilder[source]
Configure the history store.
Noneclears the prior value.- Parameters:
store (Any)
- Return type:
- dispatch_via(dispatcher: Any) DurableExecutionEngineBuilder[source]
Configure the dispatcher.
Noneclears the prior value.- Parameters:
dispatcher (Any)
- Return type:
- idempotency_key_default(key: str | None) DurableExecutionEngineBuilder[source]
Set the default
idempotency_keyapplied toexecutecalls.- Parameters:
key (str | None)
- Return type:
- runtime(runtime_factory: Callable[[...], Any] | None = None) DurableExecutionEngineBuilder[source]
Set the runtime factory.
Passing
runtime_factory=None(or omitting the call entirely) defers to the defaultAsyncLocalRuntime. Pass a custom callable when anAsyncLocalRuntimesubclass or a compatible alternative is required.- Parameters:
- Return type:
- execution_mode(mode: Literal['in_process_only', 'dispatch_only', 'both']) DurableExecutionEngineBuilder[source]
Set the execution-routing contract for
execute()(issue #882)."in_process_only"Run the workflow in-process; skip the dispatcher even if
dispatch_via()is also set. Eliminates the enqueue/in-process race entirely. The dispatcher (if configured) is retained on the engine for inspection (engine.dispatcher) butexecute()does not enqueue."dispatch_only"Enqueue the task; do NOT invoke the wrapped runtime.
execute()returns({}, schedule_id). Requires a dispatcher —build()raisesValueErrorif no dispatcher is configured."both"Enqueue AND run in-process. The two paths race; W1 checkpoint resume + dispatcher PRIMARY KEY idempotency contain the blast radius for the in-tree
SQLTaskQueueDispatcher. Custom Dispatchers MUST honor the sameidempotency_keyresume contract. Requires a dispatcher —build()raises if absent.
Calling
.execution_mode(None)(or omitting the call) defers to the auto-detect atbuild()time:"both"when a dispatcher is configured,"in_process_only"otherwise. This is the pre-issue-882 default and matches existing call-site behaviour.- Parameters:
mode (Literal['in_process_only', 'dispatch_only', 'both'])
- Return type:
- runtime_kwargs(kwargs: Mapping[str, Any]) DurableExecutionEngineBuilder[source]
Override base kwargs forwarded to the runtime factory.
checkpoint_store/checkpoint_after_each_node/history_storeare added bybuild()AFTER these kwargs, so they always win over conflicting entries here. Use this formax_concurrent_nodes,execution_timeout,user_context, etc.- Parameters:
- Return type:
- build() DurableExecutionEngine[source]
Construct the immutable
DurableExecutionEngine.Lazy-imports
AsyncLocalRuntimeas the default factory. The lazy import keepskailash.runtime.durableimportable whenasync_localis not yet available (cyclic-import safety) and matches the W1 / W2 module-load contracts.- Return type:
The dispatcher module provides a pluggable Dispatcher ABC that WorkflowScheduler accepts via dispatch_via= to route scheduled workflow firings through a task queue (e.g., for distributed execution across worker pools).
Dispatcher protocol for workflow scheduling.
Defines the abstract base class Dispatcher that connects
WorkflowScheduler to a task queue,
plus the canonical Task dataclass workers consume.
When a scheduler is constructed with dispatch_via=<dispatcher>, the
fire-time callback enqueues a Task instead of executing in-process.
A worker pool can then poll the dispatcher and execute the workflow
against its own runtime.
Idempotency is enforced at the queue layer via task_id: a stable
hash of (schedule_id, planned_fire_time_iso). A multi-instance
scheduler that double-fires produces the SAME task_id – the queue
adapter MUST treat the duplicate as “already enqueued, skip” without
raising to the caller.
- Resume contract (informational, NOT a hard coupling):
Workers SHOULD pass
task_idas theidempotency_keytoruntime.execute(...)when paired with a checkpoint store. This enables resume-from-checkpoint semantics on crash recovery. Seespecs/scheduling.mdfor the full contract.
Module: kailash.runtime.dispatcher
Added in: v0.13.x (issue #859)
- class kailash.runtime.dispatcher.Dispatcher[source]
Bases:
ABCAbstract base class for workflow dispatchers.
A Dispatcher routes a fire-time
Taskto a queue (or other transport) so that one or more workers can poll, execute, and acknowledge. The contract is intentionally minimal: enqueue is idempotent ontask_id; poll yields claimed tasks one at a time; ack marks a task complete; nack returns it for retry or dead-letters it.Implementers MUST:
Make
enqueue()idempotent ontask_id– a duplicate enqueue with the sametask_idMUST be a silent no-op (no exception to the caller).Make
poll()atomic – two concurrent workers polling the same queue MUST NOT receive the same task.On
nack(), decide based on attempt count whether to requeue (transient failure) or dead-letter (max attempts exceeded).
Reference implementation:
SQLTaskQueue.- abstractmethod async enqueue(task: Task) None[source]
Add a task to the queue.
MUST be idempotent on
task.task_id. Duplicate enqueue with the same task_id is a silent no-op – the dispatcher catches the PRIMARY KEY constraint violation and returns success.
- abstractmethod poll(queue_name: str = 'default') AsyncIterator[Task][source]
Yield tasks claimed from the queue, one at a time.
Each yielded task is in
processingstatus and locked to the polling worker. The worker MUST eventually callack()(success) ornack()(failure) for each task to release the lock.
- abstractmethod async ack(task_id: str) None[source]
Mark a task as completed.
- Parameters:
task_id (str) – The task to ack.
- Return type:
None
- class kailash.runtime.dispatcher.Task(task_id: str, schedule_id: str, workflow_blob: bytes, planned_fire_time: str, queue_name: str = 'default', kwargs: Dict[str, ~typing.Any]=<factory>)[source]
Bases:
objectA scheduled workflow task ready for dispatch.
The
Taskis the unit of work passed from the scheduler to the dispatcher and from the dispatcher to a worker. It carries enough context for the worker to execute the workflow and ack/nack the result, including the deterministictask_idused for both queue-layer dedup AND (informationally) for runtime-side idempotency on the worker.Frozen per EATP P10 — Task instances flow across the queue boundary and MUST NOT be mutated after construction; the queue payload is the canonical state.
- Variables:
task_id (str) – Stable hash of
(schedule_id, planned_fire_time_iso). Used as the queue’s PRIMARY KEY for idempotent enqueue AND as the canonicalidempotency_keyworkers SHOULD pass toruntime.execute(...)when paired with a checkpoint store.schedule_id (str) – The scheduler-assigned schedule identifier.
workflow_blob (bytes) – The JSON-serialized workflow representation produced by
Workflow.to_dict()and encoded as UTF-8. Workers MUST deserialize viaWorkflow.from_dict(json.loads(...))— NOTpickle.loads(). Pickled payloads on a queue accessible to arbitrary INSERT actors are remote-code-execution primitives; this contract uses JSON to structurally prevent that class perrules/security.md§ “No arbitrary-code execution on user input” (the same threat class).planned_fire_time (str) – The trigger’s intended fire time (UTC ISO 8601 string).
queue_name (str) – Logical queue name for routing (default
"default").kwargs (Dict[str, Any]) – Additional kwargs forwarded to
runtime.execute(...).
- Parameters:
- __post_init__() None[source]
Validate
queue_nameagainst the canonical queue-name policy.The scheduler-dispatcher path (#859) and the distributed-runtime path (#911) both carry a
queue_namefield. Pre-#911-Shard-2 only the distributed path validated; an invalid name in thisTaskcould ride through to a downstream bridge and silently strand work on a malformed Redis key. Validating here closes the bypass at the dispatcher boundary so the dataclass cannot be constructed in an unsafe state.Issue #911 Shard 2 followup — R1-006 redteam finding.
- Return type:
None
- kailash.runtime.dispatcher.compute_task_id(schedule_id: str, planned_fire_time: datetime) str[source]
Compute the stable, deterministic
task_idfor a fire event.The task_id is a SHA-256 hash of
schedule_id || planned_fire_time_iso, truncated to 32 hex chars (128 bits of collision resistance). The same (schedule_id, planned_fire_time) pair ALWAYS produces the same task_id; this is what makes queue-layer dedup work for multi-instance scheduler double-fire scenarios.- Parameters:
- Returns:
A 32-character lowercase hex string. Suitable for use as a PRIMARY KEY in the task queue table.
- Return type:
Notes
The ISO 8601 representation is used because it’s the canonical cross-language wire format and preserves microsecond precision when present. Naive datetimes (without tzinfo) and aware datetimes in different timezones produce different task_ids – callers MUST be consistent about timezone awareness within a single schedule.
CyclicWorkflowExecutor
Specialized executor for workflows containing cycles.
- class kailash.workflow.cyclic_runner.CyclicWorkflowExecutor(safety_manager: CycleSafetyManager | None = None)[source]
Bases:
objectExecution engine supporting cyclic workflows with fixed parameter propagation.
- Parameters:
safety_manager (CycleSafetyManager | None)
- __init__(safety_manager: CycleSafetyManager | None = None)[source]
Initialize cyclic workflow executor.
- Parameters:
safety_manager (CycleSafetyManager | None) – Optional safety manager for resource limits
- execute(workflow: Workflow, parameters: dict[str, Any] | None = None, task_manager: TaskManager | None = None, run_id: str | None = None, runtime=None) tuple[dict[str, Any], str][source]
Execute workflow with cycle support.
- Parameters:
workflow (Workflow) – Workflow to execute
parameters (dict[str, Any] | None) – Initial parameters/overrides
task_manager (TaskManager | None) – Optional task manager for tracking execution
run_id (str | None) – Optional run ID to use (if not provided, one will be generated)
runtime – Optional runtime instance for enterprise features (LocalRuntime)
- Returns:
Tuple of (results dict, run_id)
- Raises:
WorkflowExecutionError – If execution fails
WorkflowValidationError – If workflow is invalid
- Return type:
Characteristics:
Optimized for iterative workflows
Automatic convergence detection
State management between iterations
Performance tracking per cycle
Memory-efficient execution
Example Usage:
from kailash.workflow.cyclic_runner import CyclicWorkflowExecutor
from kailash import Workflow
# Create workflow with cycles
workflow = Workflow("optimization")
workflow.add_node("optimizer", OptimizerNode())
workflow.create_cycle("optimization_cycle") \
.connect("optimizer", "optimizer") \
.max_iterations(100) \
.converge_when("converged == True") \
.build()
# Execute with cyclic executor
executor = CyclicWorkflowExecutor()
results = executor.execute(workflow, inputs={"optimizer": {"initial_value": 0.1}})
# Access cycle metrics
print(f"Iterations: {results['cycle_metrics']['total_iterations']}")
print(f"Converged: {results['cycle_metrics']['converged']}")
Performance Characteristics:
Minimal overhead: ~0.03ms per iteration
Efficient state management with automatic cleanup
Support for parallel cycles with ParallelCyclicRuntime
Configurable history windows for memory optimization
ParallelCyclicRuntime
Parallel execution engine for cyclic workflows.
- class kailash.runtime.parallel_cyclic.ParallelCyclicRuntime(debug: bool = False, max_workers: int = 4, enable_cycles: bool = True, enable_async: bool = True, runtime=None)[source]
Bases:
objectEnhanced parallel runtime with support for cyclic workflows and concurrent execution.
- __init__(debug: bool = False, max_workers: int = 4, enable_cycles: bool = True, enable_async: bool = True, runtime=None)[source]
Initialize the parallel cyclic runtime.
- execute(workflow: Workflow, task_manager: TaskManager | None = None, parameters: dict[str, dict[str, Any]] | None = None, parallel_nodes: set[str] | None = None, *, soft_time_limit: float | None = None, time_limit: float | None = None, **kwargs: Any) tuple[dict[str, Any], str | None][source]
Execute a workflow with parallel and cyclic support.
- Parameters:
workflow (Workflow) – Workflow to execute
task_manager (TaskManager | None) – Optional task manager for tracking
parameters (dict[str, dict[str, Any]] | None) – Optional parameter overrides per node
parallel_nodes (set[str] | None) – Set of node IDs that can be executed in parallel
soft_time_limit (float | None) – Optional advisory deadline in seconds (#912 Shard 1 slot; enforcement lands Shard 2).
time_limit (float | None) – Optional unconditional kill deadline in seconds.
**kwargs (Any) – Forward-compatibility kwargs for additive #912 Shard 1 contract.
- Returns:
Tuple of (results dict, run_id)
- Raises:
RuntimeExecutionError – If execution fails
WorkflowValidationError – If workflow is invalid
- Return type:
Features:
Parallel execution of independent cycle branches
Shared state management across workers
Automatic load balancing
Thread-safe cycle state updates
Example Usage:
from kailash.runtime.parallel_cyclic import ParallelCyclicRuntime
# Create runtime for parallel cycles
runtime = ParallelCyclicRuntime(
max_workers=4,
cycle_batch_size=10 # Process 10 iterations per batch
)
# Execute workflow with multiple independent cycles
results = runtime.execute(workflow)
ParallelRuntime
Parallel execution using multiprocessing.
- class kailash.runtime.parallel.ParallelRuntime(max_workers: int = 8, debug: bool = False)[source]
Bases:
objectParallel execution engine for workflows.
This runtime provides true concurrent execution of independent nodes in a workflow, allowing for maximum performance with both synchronous and asynchronous nodes.
Key features: - Concurrent execution of independent nodes - Dynamic scheduling based on dependency resolution - Support for both sync and async nodes - Configurable parallelism limits - Detailed execution metrics and visualization
- Usage:
runtime = ParallelRuntime(max_workers=8) results, run_id = await runtime.execute(workflow, parameters={…})
- async execute(workflow: Workflow, task_manager: TaskManager | None = None, parameters: dict[str, dict[str, Any]] | None = None, *, soft_time_limit: float | None = None, time_limit: float | None = None, **kwargs: Any) tuple[dict[str, Any], str | None][source]
Execute a workflow with parallel node execution.
- Parameters:
workflow (Workflow) – Workflow to execute
task_manager (TaskManager | None) – Optional task manager for tracking
parameters (dict[str, dict[str, Any]] | None) – Optional parameter overrides per node
soft_time_limit (float | None) – Optional advisory deadline in seconds (#912 Shard 1 slot; enforcement lands Shard 2).
time_limit (float | None) – Optional unconditional kill deadline in seconds.
**kwargs (Any) – Forward-compatibility kwargs for additive #912 Shard 1 contract.
- Returns:
Tuple of (results dict, run_id)
- Raises:
RuntimeExecutionError – If execution fails
WorkflowValidationError – If workflow is invalid
- Return type:
Configuration Options:
from kailash.runtime import ParallelRuntime
runtime = ParallelRuntime(
max_workers=4, # Number of worker processes
chunk_size=1000, # Data chunk size for parallelization
memory_limit="2GB", # Memory limit per worker
timeout=300 # Execution timeout in seconds
)
Example Usage:
workflow = Workflow("parallel_example")
# Configure parallel execution
workflow.add_node("DataProcessor", "process", config={
"parallel": True,
"chunk_column": "id",
"chunks": 10
})
runtime = ParallelRuntime(max_workers=8)
results = runtime.execute(workflow)
Best Practices:
CPU-Bound Tasks: Use for computationally intensive operations
Data Parallelism: Split large datasets into chunks
Resource Management: Monitor memory usage per worker
Avoid Shared State: Ensure nodes are stateless
DockerRuntime
Execute nodes in isolated Docker containers.
- class kailash.runtime.docker.DockerRuntime(base_image: str = 'python:3.11-slim', network_name: str = 'kailash-network', work_dir: str | None = None, sdk_path: str | None = None, resource_limits: dict[str, str] | None = None, task_manager: TaskManager | None = None)[source]
Bases:
objectDocker-based runtime for executing workflows.
This runtime executes each node in a separate Docker container, handling dependencies, data passing, and workflow orchestration.
- Parameters:
- __init__(base_image: str = 'python:3.11-slim', network_name: str = 'kailash-network', work_dir: str | None = None, sdk_path: str | None = None, resource_limits: dict[str, str] | None = None, task_manager: TaskManager | None = None)[source]
Initialize the Docker runtime.
- Parameters:
base_image (str) – Base Docker image to use for nodes.
network_name (str) – Docker network name for container communication.
work_dir (str | None) – Working directory for Docker files.
sdk_path (str | None) – Path to the Kailash SDK source.
resource_limits (dict[str, str] | None) – Default resource limits for containers.
task_manager (TaskManager | None) – Task manager for tracking workflow execution.
- execute(workflow: Workflow, inputs: dict[str, dict[str, Any]] | None = None, node_resource_limits: dict[str, dict[str, str]] | None = None, *, soft_time_limit: float | None = None, time_limit: float | None = None, **kwargs: Any) tuple[dict[str, dict[str, Any]], str | None][source]
Execute a workflow using Docker containers.
- Parameters:
workflow (Workflow) – The workflow to execute.
inputs (dict[str, dict[str, Any]] | None) – The inputs for each node.
node_resource_limits (dict[str, dict[str, str]] | None) – Resource limits for specific nodes.
soft_time_limit (float | None) – Optional advisory deadline in seconds (#912 Shard 1 slot; out-of-process timer enforcement lands later).
time_limit (float | None) – Optional unconditional kill deadline in seconds.
**kwargs (Any) – Forward-compatibility kwargs for additive #912 Shard 1 contract.
- Returns:
Tuple of (execution_results, run_id).
- Return type:
Configuration:
from kailash.runtime import DockerRuntime
runtime = DockerRuntime(
base_image="python:3.9-slim",
network_mode="bridge",
volumes={
"/local/data": {
"bind": "/container/data",
"mode": "rw"
}
},
environment={
"PYTHONPATH": "/app"
}
)
Node-Specific Docker Config:
workflow.add_node("PythonCodeNode", "secure_execution", config={
"code": "...",
"docker": {
"image": "sandboxed-python:latest",
"memory": "512m",
"cpu_shares": 512,
"read_only": True
}
})
Security Features:
Process isolation
Resource limits (CPU, memory)
Network isolation
Filesystem sandboxing
Custom security profiles
TestingRuntime
Note
🚧 Coming Soon - This runtime is planned for a future release.
Planned Features: - Mock runtime for unit testing workflows - Configurable node behavior simulation - Deterministic test results - Performance testing capabilities
Alternative: Use the local runtime with mock data for testing in the meantime.
Example Usage:
from kailash.runtime import TestingRuntime
# Configure mock responses
runtime = TestingRuntime()
runtime.set_node_response("read_data", {
"data": pd.DataFrame({"id": [1, 2, 3], "value": [10, 20, 30]})
})
runtime.set_node_response("process", {
"result": {"total": 60}
})
# Test workflow execution
results = runtime.execute(workflow)
assert results["process"]["result"]["total"] == 60
Testing Patterns:
import pytest
from kailash.runtime import TestingRuntime
@pytest.fixture
def mock_runtime():
runtime = TestingRuntime()
# Configure common responses
runtime.set_node_response("api_call", {
"status": 200,
"data": {"message": "success"}
})
# Simulate errors
runtime.set_node_error("failing_node",
ValueError("Simulated error"))
return runtime
def test_workflow(mock_runtime):
workflow = create_workflow()
results = mock_runtime.execute(workflow)
# Verify execution
assert mock_runtime.get_execution_order() == [
"input", "process", "output"
]
assert mock_runtime.get_node_call_count("process") == 1
Runtime Comparison
Feature |
Local |
Async |
Parallel |
Docker |
|---|---|---|---|---|
Execution Model |
Sequential |
Concurrent |
Parallel |
Isolated |
Best For |
Development |
I/O Tasks |
CPU Tasks |
Security |
Overhead |
Low |
Low |
Medium |
High |
Resource Limits |
No |
No |
Yes |
Yes |
Isolation |
None |
None |
Process |
Container |
Custom Runtime Development
Create custom runtimes by extending the base class:
from kailash.runtime.runner import BaseRuntime
from typing import Dict, Any
class CloudRuntime(BaseRuntime):
"""Execute workflows in cloud environments."""
def __init__(self, cloud_config: dict):
super().__init__()
self.cloud_config = cloud_config
self.client = self._init_cloud_client()
def execute_node(self, node, inputs: Dict[str, Any]) -> Dict[str, Any]:
"""Execute a single node in the cloud."""
# Package node and inputs
payload = {
"node_type": type(node).__name__,
"config": node.config,
"inputs": inputs
}
# Submit to cloud execution service
job_id = self.client.submit_job(payload)
# Wait for completion
result = self.client.wait_for_job(job_id)
return result
def execute(self, workflow) -> Dict[str, Any]:
"""Execute entire workflow in the cloud."""
# Upload workflow definition
workflow_id = self.client.upload_workflow(workflow.to_dict())
# Execute remotely
execution_id = self.client.execute_workflow(workflow_id)
# Monitor and return results
return self.client.get_results(execution_id)
Performance Optimization
Local Runtime
# Use caching for repeated operations
workflow.add_node("CachedDataReader", "read", config={
"file_path": "large_file.csv",
"cache": True,
"cache_ttl": 3600 # 1 hour
})
Async Runtime
# Batch async operations
workflow.add_node("BatchAPIClient", "fetch", config={
"urls": ["url1", "url2", "url3"],
"batch_size": 10,
"max_concurrent": 5
})
Parallel Runtime
# Optimize chunk size
from kailash.runtime import ParallelRuntime
# Calculate optimal chunk size
data_size = 1_000_000
num_workers = 8
chunk_size = data_size // (num_workers * 4) # 4 chunks per worker
runtime = ParallelRuntime(
max_workers=num_workers,
chunk_size=chunk_size
)
Docker Runtime
# Pre-build images with dependencies
runtime = DockerRuntime(
base_image="myapp:latest", # Pre-built with all dependencies
pull_policy="if_not_present",
warm_containers=2 # Keep containers warm
)
Error Handling
Different runtimes handle errors differently:
from kailash.runtime import RuntimeError
try:
results = runtime.execute(workflow)
except RuntimeError as e:
print(f"Runtime error: {e}")
# Access error details
if hasattr(e, "node_id"):
print(f"Failed at node: {e.node_id}")
if hasattr(e, "partial_results"):
print(f"Completed nodes: {list(e.partial_results.keys())}")
Runtime-Specific Error Handling:
# Async runtime
try:
results = await async_runtime.execute_async(workflow)
except asyncio.TimeoutError:
print("Async execution timed out")
# Parallel runtime
try:
results = parallel_runtime.execute(workflow)
except MemoryError:
print("Worker ran out of memory")
# Reduce chunk size or worker count
# Docker runtime
try:
results = docker_runtime.execute(workflow)
except DockerException as e:
print(f"Container error: {e}")
# Check container logs
logs = docker_runtime.get_container_logs(e.container_id)
Monitoring and Metrics
Track runtime performance:
from kailash.runtime import RuntimeMetrics
# Enable metrics collection
with LocalRuntime(collect_metrics=True) as runtime:
# Execute workflow
results = runtime.execute(workflow)
# Access metrics
metrics = runtime.get_metrics()
print(f"Total execution time: {metrics.total_time}s")
print(f"Node execution times: {metrics.node_times}")
print(f"Memory usage: {metrics.memory_usage}")
# Export metrics
metrics.export("runtime_metrics.json")