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, ConditionalExecutionMixin

Unified 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)

  • resource_limits (dict[str, Any] | None)

  • 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. Default None waits 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 every SYNC_BRIDGE_WATCHDOG_INTERVAL seconds instead of hanging mutely. Set a value to convert that into a RuntimeExecutionError naming 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_timeout is 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 NodeCompletionEvent per node, regardless of whether the node succeeded or failed; the event’s error field carries the failure repr when set. The runtime applies redact_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:

Any

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 + grace elapses, the wrapper raises HardTimeLimitExceeded regardless of soft-limit acknowledgement.

  • cancellation_token (CancellationToken | None)

  • search_attributes (Dict[str, Any] | 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_limit elapses and the workflow does not exit before the hard deadline. Catch to save partial work / write a checkpoint / exit cleanly.

  • HardTimeLimitExceeded – If time_limit + grace elapses regardless of soft-limit acknowledgement. Operators rely on this path to bound runaway resource consumption.

Return type:

tuple[dict[str, Any], str | None]

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:

tuple[dict[str, Any], str | None]

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:
  • workflow_id (str) – The run_id or workflow_id of the target workflow.

  • signal_name (str) – Name of the signal to send.

  • data (Any) – Arbitrary data payload to deliver with the signal.

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:
  • workflow_id (str) – The run_id or workflow_id of the target workflow.

  • query_name (str) – Name of the query to execute.

  • **kwargs (Any) – Keyword arguments passed to the query handler.

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:

Any

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 LocalRuntime across many execute() calls (e.g. DataFlow’s ModelRegistry, DataFlow instance, migration inspectors) should call this method immediately after construction. The runtime will then:

  • NOT emit the “use context manager” DeprecationWarning on execute() — that warning targets transient ad-hoc callers, not frameworks with their own shutdown protocol.

  • NOT register an atexit cleanup handler for the persistent event loop — the owner is responsible for calling close() at its own shutdown.

The caller MUST invoke close() (or route cleanup through ShutdownCoordinator) when the owning framework tears down.

Returns:

self to support fluent construction:

self.runtime = LocalRuntime().mark_externally_managed()

Return type:

LocalRuntime

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:

LocalRuntime

release() None[source]

Decrement reference count. Alias for close().

Actual cleanup happens when count reaches 0.

Return type:

None

property ref_count: int

Current reference count (for debugging/testing).

__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:

LocalRuntime

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.

Returns:

Dictionary containing performance and security metrics

Return type:

Dict[str, Any]

reset_validation_metrics() None[source]

Reset validation metrics collector.

Return type:

None

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:
  • node – Node instance to execute

  • node_id (str) – Node identifier for tracking

  • inputs (dict[str, Any]) – Input parameters for node execution

  • **execution_kwargs – Additional execution parameters

Returns:

Node execution result

Raises:

Various enterprise exceptions based on configured policies

Return type:

Any

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.

Parameters:
Return type:

Any

get_resource_metrics() dict[str, Any] | None[source]

Get current resource usage metrics from the resource enforcer.

Returns:

Dict containing resource metrics or None if no resource enforcer

Return type:

dict[str, Any] | None

get_execution_metrics(run_id: str) dict[str, Any] | None[source]

Get execution metrics for a specific run ID.

Parameters:

run_id (str) – The run ID to get metrics for

Returns:

Dict containing execution metrics or None if not available

Return type:

dict[str, Any] | None

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

reset_retry_metrics()[source]

Reset all retry metrics and analytics data.

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.

Parameters:
  • workflow (Workflow) – Workflow to create execution plan for

  • switch_results (Dict[str, Dict[str, Any]]) – Results from SwitchNode execution

Returns:

Cached or newly computed execution plan

Return type:

List[str] | tuple[str, …]

get_execution_analytics() Dict[str, Any][source]

Get comprehensive execution analytics for monitoring and optimization.

Returns:

Dictionary containing detailed analytics data

Return type:

Dict[str, Any]

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.

Returns:

Dictionary containing health check results

Return type:

Dict[str, Any]

optimize_runtime_performance() Dict[str, Any][source]

Optimize runtime performance based on analytics data.

Returns:

Dictionary describing optimizations applied

Return type:

Dict[str, Any]

get_performance_report() Dict[str, Any][source]

Get performance monitoring report.

Returns:

Performance statistics and recommendations

Return type:

Dict[str, Any]

generate_compatibility_report(workflow: Workflow) Dict[str, Any][source]

Generate compatibility report for a workflow.

Parameters:

workflow (Workflow) – Workflow to analyze

Returns:

Compatibility report dictionary

Return type:

Dict[str, Any]

get_compatibility_report_markdown(workflow: Workflow) str[source]

Generate compatibility report in markdown format.

Parameters:

workflow (Workflow) – Workflow to analyze

Returns:

Markdown formatted report

Return type:

str

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.

Returns:

Debug information including execution decisions and paths

Return type:

Dict[str, Any]

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

async get_shared_connection_pool(pool_name: str, pool_config: Dict[str, Any]) Any[source]

Get shared connection pool for database operations.

Parameters:
  • pool_name (str) – Name for the connection pool

  • pool_config (Dict[str, Any]) – Pool configuration parameters

Returns:

Connection pool instance

Raises:
Return type:

Any

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:

bool

get_runtime_metrics() Dict[str, Any][source]

Get comprehensive runtime health and performance metrics.

Returns:

Dictionary containing runtime metrics across all categories.

Return type:

Dict[str, Any]

get_health_status() Dict[str, Any][source]

Get current health status of the runtime.

Returns:

Health status information including overall status and details.

Return type:

Dict[str, Any]

property connection_pool_manager

Access the connection pool manager.

async cleanup()[source]

Clean up runtime resources.

__repr__() str

Get string representation of runtime instance.

Returns:

String representation with key configuration

Return type:

str

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:

list[str]

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: LocalRuntime

Async-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:
  • resource_registry (ResourceRegistry | None)

  • max_concurrent_nodes (int)

  • enable_analysis (bool)

  • enable_profiling (bool)

  • thread_pool_size (int)

  • execution_timeout (int | None)

__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 SoftTimeLimitExceeded when reached; user code MAY catch and exit cleanly.

  • time_limit (float | None) – Optional unconditional kill deadline in seconds (#912). Raises HardTimeLimitExceeded after time_limit + grace regardless of acknowledgement.

  • cancellation_token (Any)

  • search_attributes (Dict[str, Any] | None)

  • 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_limit elapses.

  • HardTimeLimitExceeded – If time_limit + grace elapses.

Return type:

Tuple[Dict[str, Any], str | None]

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:
  • workflow – Workflow to execute

  • task_manager (TaskManager | None) – Optional task manager for tracking

  • parameters (Dict[str, Any] | None) – Input parameters for the workflow

  • cancellation_token (Any)

  • execution_tracker (Any)

  • search_attributes (Dict[str, Any] | None)

  • kwargs (Any)

Returns:

Tuple of (results dict, run_id)

Return type:

Tuple[Dict[str, Any], str | None]

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

  • inputs (Dict[str, Any]) – Input data for the workflow

  • context (ExecutionContext | None) – Optional execution context

  • soft_time_limit (float | None) – Optional advisory deadline in seconds (#912). Raises SoftTimeLimitExceeded when reached; user code MAY catch and exit cleanly.

  • time_limit (float | None) – Optional unconditional kill deadline in seconds (#912). Raises HardTimeLimitExceeded after time_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:

Tuple[Dict[str, Any], str]

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_limit elapses.

  • HardTimeLimitExceeded – If time_limit + grace elapses.

Parameters:
  • inputs (Dict[str, Any])

  • context (ExecutionContext | None)

  • idempotency_key (str | None)

  • force_resume_with_drift (bool)

  • soft_time_limit (float | None)

  • time_limit (float | None)

Return type:

Tuple[Dict[str, Any], str]

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:

AsyncLocalRuntime

__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:

LocalRuntime

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:

str

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:

LocalRuntime

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:

bool

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:
  • node – Node instance to execute

  • node_id (str) – Node identifier for tracking

  • inputs (dict[str, Any]) – Input parameters for node execution

  • **execution_kwargs – Additional execution parameters

Returns:

Node execution result

Raises:

Various enterprise exceptions based on configured policies

Return type:

Any

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.

Parameters:
Return type:

Any

generate_compatibility_report(workflow: Workflow) Dict[str, Any]

Generate compatibility report for a workflow.

Parameters:

workflow (Workflow) – Workflow to analyze

Returns:

Compatibility report dictionary

Return type:

Dict[str, Any]

get_compatibility_report_markdown(workflow: Workflow) str

Generate compatibility report in markdown format.

Parameters:

workflow (Workflow) – Workflow to analyze

Returns:

Markdown formatted report

Return type:

str

get_execution_analytics() Dict[str, Any]

Get comprehensive execution analytics for monitoring and optimization.

Returns:

Dictionary containing detailed analytics data

Return type:

Dict[str, Any]

get_execution_metrics(run_id: str) dict[str, Any] | None

Get execution metrics for a specific run ID.

Parameters:

run_id (str) – The run ID to get metrics for

Returns:

Dict containing execution metrics or None if not available

Return type:

dict[str, Any] | None

get_execution_path_debug_info() Dict[str, Any]

Get detailed debug information about execution paths.

Returns:

Debug information including execution decisions and paths

Return type:

Dict[str, Any]

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.

Parameters:
  • workflow (Workflow) – Workflow to create execution plan for

  • switch_results (Dict[str, Dict[str, Any]]) – Results from SwitchNode execution

Returns:

Cached or newly computed execution plan

Return type:

List[str] | tuple[str, …]

get_health_diagnostics() Dict[str, Any]

Get health diagnostics for monitoring system health.

Returns:

Dictionary containing health check results

Return type:

Dict[str, Any]

get_health_status() Dict[str, Any]

Get current health status of the runtime.

Returns:

Health status information including overall status and details.

Return type:

Dict[str, Any]

get_performance_report() Dict[str, Any]

Get performance monitoring report.

Returns:

Performance statistics and recommendations

Return type:

Dict[str, Any]

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.

Returns:

Dict containing resource metrics or None if no resource enforcer

Return type:

dict[str, Any] | None

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_runtime_metrics() Dict[str, Any]

Get comprehensive runtime health and performance metrics.

Returns:

Dictionary containing runtime metrics across all categories.

Return type:

Dict[str, Any]

async get_shared_connection_pool(pool_name: str, pool_config: Dict[str, Any]) Any

Get shared connection pool for database operations.

Parameters:
  • pool_name (str) – Name for the connection pool

  • pool_config (Dict[str, Any]) – Pool configuration parameters

Returns:

Connection pool instance

Raises:
Return type:

Any

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

get_validation_metrics() Dict[str, Any]

Get validation performance metrics for the runtime.

Returns:

Dictionary containing performance and security metrics

Return type:

Dict[str, Any]

mark_externally_managed() LocalRuntime

Declare that an owning framework manages this runtime’s lifecycle.

Frameworks that hold a long-lived LocalRuntime across many execute() calls (e.g. DataFlow’s ModelRegistry, DataFlow instance, migration inspectors) should call this method immediately after construction. The runtime will then:

  • NOT emit the “use context manager” DeprecationWarning on execute() — that warning targets transient ad-hoc callers, not frameworks with their own shutdown protocol.

  • NOT register an atexit cleanup handler for the persistent event loop — the owner is responsible for calling close() at its own shutdown.

The caller MUST invoke close() (or route cleanup through ShutdownCoordinator) when the owning framework tears down.

Returns:

self to support fluent construction:

self.runtime = LocalRuntime().mark_externally_managed()

Return type:

LocalRuntime

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 NodeCompletionEvent per node, regardless of whether the node succeeded or failed; the event’s error field carries the failure repr when set. The runtime applies redact_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:

Any

optimize_runtime_performance() Dict[str, Any]

Optimize runtime performance based on analytics data.

Returns:

Dictionary describing optimizations applied

Return type:

Dict[str, Any]

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:
  • workflow_id (str) – The run_id or workflow_id of the target workflow.

  • query_name (str) – Name of the query to execute.

  • **kwargs (Any) – Keyword arguments passed to the query handler.

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:

Any

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)

property ref_count: int

Current reference count (for debugging/testing).

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.

reset_validation_metrics() None

Reset validation metrics collector.

Return type:

None

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:
  • workflow_id (str) – The run_id or workflow_id of the target workflow.

  • signal_name (str) – Name of the signal to send.

  • data (Any) – Arbitrary data payload to deliver with the signal.

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:

list[str]

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: ABC

Base 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:

BaseRuntime

__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 + grace elapses, the wrapper raises HardTimeLimitExceeded regardless 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:
  1. Generate run_id using self._generate_run_id()

  2. Initialize metadata using self._initialize_execution_metadata()

  3. Validate workflow before execution

  4. Execute nodes in proper order

  5. Collect and return results

  6. 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
__repr__() str[source]

Get string representation of runtime instance.

Returns:

String representation with key configuration

Return type:

str

Example

>>> runtime = LocalRuntime(debug=True, enable_cycles=True)
>>> print(repr(runtime))
<LocalRuntime(id=runtime_..., debug=True, cycles=True, async=False)>

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: object

Trust 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']
trace_id: str
human_origin: Any | None = None
delegation_chain: List[str]
delegation_depth: int = 0
constraints: Dict[str, Any]
verification_mode: TrustVerificationMode = 'disabled'
workflow_id: str | None = None
node_path: List[str]
metadata: Dict[str, Any]
created_at: datetime
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:

RuntimeTrustContext

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:

RuntimeTrustContext

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

Returns:

Dictionary representation of the context

Return type:

Dict[str, Any]

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:

RuntimeTrustContext

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:

RuntimeTrustContext

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
Parameters:
Return type:

None

class kailash.runtime.trust.TrustVerificationMode(value)[source]

Bases: Enum

Trust 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:

Iterator[RuntimeTrustContext]

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: object

Result 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:
  • allowed (bool)

  • reason (str | None)

  • constraints (Dict[str, Any])

  • capability_used (str | None)

  • trace_id (str | None)

Example

>>> result = VerificationResult(allowed=True, reason="Access granted")
>>> if result:  # Uses __bool__
...     print("Allowed!")
Allowed!
allowed: bool
reason: str | None = None
constraints: Dict[str, Any]
capability_used: str | None = None
trace_id: str | None = None
__bool__() bool[source]

Allow using result directly in boolean context.

Returns:

True if allowed, False otherwise

Return type:

bool

to_dict() Dict[str, Any][source]

Serialize result to dictionary.

Returns:

Dictionary representation of the result

Return type:

Dict[str, Any]

__init__(allowed: bool, reason: str | None = None, constraints: Dict[str, ~typing.Any]=<factory>, capability_used: str | None = None, trace_id: str | None = None) None
Parameters:
  • allowed (bool)

  • reason (str | None)

  • constraints (Dict[str, Any])

  • capability_used (str | None)

  • trace_id (str | None)

Return type:

None

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: object

Configuration 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:
  • mode (str)

  • cache_enabled (bool)

  • cache_ttl_seconds (int)

  • fallback_allow (bool | None)

  • audit_denials (bool)

  • high_risk_nodes (List[str])

Example

>>> config = TrustVerifierConfig(
...     mode="enforcing",
...     cache_ttl_seconds=120,
...     high_risk_nodes=["BashCommand", "FileWrite"],
... )
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]
__init__(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>) None
Parameters:
  • mode (str)

  • cache_enabled (bool)

  • cache_ttl_seconds (int)

  • fallback_allow (bool | None)

  • audit_denials (bool)

  • high_risk_nodes (List[str])

Return type:

None

class kailash.runtime.trust.TrustVerifier(kaizen_backend: Any | None = None, config: TrustVerifierConfig | None = None)[source]

Bases: object

Bridge 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:
__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_enabled: bool

Check if verification is enabled.

Returns:

True if mode is not DISABLED

property is_enforcing: bool

Check if verification is in enforcing mode.

Returns:

True if mode is ENFORCING

clear_cache() None[source]

Clear all cached verification results.

Return type:

None

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.

Parameters:

agent_id (str) – The agent whose cache entries should be invalidated

Returns:

Number of cache entries removed

Return type:

int

invalidate_node(node_type: str) int[source]

Invalidate all cached results for a specific node type (CARE-043).

Parameters:

node_type (str) – The node type whose cache entries should be invalidated

Returns:

Number of cache entries removed

Return type:

int

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:

VerificationResult

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:

VerificationResult

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:

VerificationResult

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: TrustVerifier

Mock 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:
__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:

VerificationResult

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:

VerificationResult

clear_cache() None

Clear all cached verification results.

Return type:

None

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.

Parameters:

agent_id (str) – The agent whose cache entries should be invalidated

Returns:

Number of cache entries removed

Return type:

int

invalidate_node(node_type: str) int

Invalidate all cached results for a specific node type (CARE-043).

Parameters:

node_type (str) – The node type whose cache entries should be invalidated

Returns:

Number of cache entries removed

Return type:

int

property is_enabled: bool

Check if verification is enabled.

Returns:

True if mode is not DISABLED

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:

VerificationResult

class kailash.runtime.trust.AuditEventType(value)[source]

Bases: str, Enum

Well-known audit event types (cross-domain union).

String-backed enum whose .value is used to populate the canonical AuditEvent.event_type field. 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 in AuditEvent.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: object

Canonical 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)

  • metadata (Dict[str, Any])

  • 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)

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, Any]
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
to_dict() Dict[str, Any][source]

Serialize to a plain dictionary.

All extended fields are included; None values are preserved so round-trips are lossless.

Return type:

Dict[str, Any]

classmethod from_dict(data: Dict[str, Any]) AuditEvent[source]

Deserialize from a plain dictionary.

Unknown extra keys are ignored. Missing optional keys default to None or the field default.

Parameters:

data (Dict[str, Any])

Return type:

AuditEvent

verify_integrity() bool[source]

Verify this event’s hash matches its content.

Uses hmac.compare_digest to 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:

bool

__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)

  • metadata (Dict[str, Any])

  • 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: object

Audit 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()
Parameters:
  • audit_store (Optional[Any])

  • enabled (bool)

  • log_to_stdout (bool)

__init__(audit_store: Any | None = None, enabled: bool = True, log_to_stdout: bool = False) None[source]

Initialize the RuntimeAuditGenerator.

Parameters:
  • audit_store (Any | None) – Optional Kaizen AuditStore for persistence

  • enabled (bool) – Whether audit generation is enabled (default True)

  • log_to_stdout (bool) – Whether to log events to stdout (default False)

Return type:

None

async workflow_started(run_id: str, workflow_name: str, trust_context: 'RuntimeTrustContext' | None) AuditEvent[source]

Record workflow start event.

Parameters:
  • run_id (str) – Unique run identifier

  • workflow_name (str) – Name of the workflow

  • trust_context (Optional['RuntimeTrustContext']) – RuntimeTrustContext for trust information

Returns:

The created AuditEvent

Return type:

AuditEvent

async workflow_completed(run_id: str, duration_ms: int, trust_context: 'RuntimeTrustContext' | None) AuditEvent[source]

Record workflow completion event.

Parameters:
  • run_id (str) – Unique run identifier

  • duration_ms (int) – Execution duration in milliseconds

  • trust_context (Optional['RuntimeTrustContext']) – RuntimeTrustContext for trust information

Returns:

The created AuditEvent

Return type:

AuditEvent

async workflow_failed(run_id: str, error: str, duration_ms: int, trust_context: 'RuntimeTrustContext' | None) AuditEvent[source]

Record workflow failure event.

Parameters:
  • run_id (str) – Unique run identifier

  • 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:

AuditEvent

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:
  • run_id (str) – Unique run identifier

  • node_id (str) – Node instance ID

  • node_type (str) – Type of node executed

  • duration_ms (int) – Execution duration in milliseconds

  • trust_context (Optional['RuntimeTrustContext']) – RuntimeTrustContext for trust information

Returns:

The created AuditEvent

Return type:

AuditEvent

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:

AuditEvent

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:

AuditEvent

async resource_accessed(run_id: str, resource: str, action: str, result: str, trust_context: 'RuntimeTrustContext' | None) AuditEvent[source]

Record resource access event.

Parameters:
  • run_id (str) – Unique run identifier

  • resource (str) – Resource path or identifier

  • action (str) – Action performed (e.g., “read”, “write”)

  • result (str) – Result of access (“success”, “failure”, “denied”)

  • trust_context (Optional['RuntimeTrustContext']) – RuntimeTrustContext for trust information

Returns:

The created AuditEvent

Return type:

AuditEvent

get_events() List[AuditEvent][source]

Get all recorded events.

Returns:

List of all AuditEvents in chronological order

Return type:

List[AuditEvent]

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:

List[AuditEvent]

get_events_by_trace(trace_id: str) List[AuditEvent][source]

Get events filtered by trace ID.

Parameters:

trace_id (str) – Trace ID to filter by

Returns:

List of matching AuditEvents

Return type:

List[AuditEvent]

clear_events() None[source]

Clear all recorded events.

Removes all events from in-memory storage. Does not affect events already persisted to Kaizen store.

Return type:

None

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 via runtime.on_node_complete(callback) receive one of these per node completion.

  • WorkflowShapeDriftError — raised when a caller resumes with an idempotency_key whose persisted checkpoint was captured against a different workflow fingerprint. Same structural-confirmation pattern as git reset --hard / force_drop / force_downgrade: refuse by default, require force_resume_with_drift=True to 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. Per rules/event-payload-classification.md MUST Rules 1–3 and the cross-cutting invariant 3.1 in workspaces/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: object

A 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. None only when the runtime ran without a task_manager AND the AsyncLocalRuntime path skipped the wall-clock-derived ID — which is rare and always recoverable from idempotency_key if 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(). See compute_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. None when the runtime is single-tenant. History/checkpoint rows MUST partition on this per rules/tenant-isolation.md MUST Rule 5.

  • idempotency_key (str | None) – The caller-supplied resume key, if any.

  • error (str | None) – None on 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:
run_id: str | None
workflow_id: str
workflow_fingerprint: str
node_id: str
node_type: str
outputs: Mapping[str, Any]
started_at: datetime
ended_at: datetime
duration_ms: int
tenant_id: str | None = None
idempotency_key: str | None = None
error: str | None = None
metadata: Mapping[str, Any]
to_dict() Dict[str, Any][source]

Serialise to a JSON-friendly dict (per EATP rule).

Return type:

Dict[str, Any]

classmethod from_dict(data: Mapping[str, Any]) NodeCompletionEvent[source]

Reconstruct from a dict produced by to_dict().

Parameters:

data (Mapping[str, Any])

Return type:

NodeCompletionEvent

__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: object

Registry 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 calls dispatch_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 an asyncio.Lock here — the existing tests will surface the race.

__init__() None[source]
Return type:

None

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]

clear() None[source]

Drop all subscribers (test helper).

Return type:

None

property subscriber_count: int

Number of registered subscribers (test helper).

async dispatch_async(event: NodeCompletionEvent) None[source]

Dispatch event to all subscribers, awaiting any coroutines.

Subscriber exceptions are logged at WARN (per rules/observability.md Rule 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.md Rule 3 we still raise CancelledError / KeyboardInterrupt / SystemExit.

Parameters:

event (NodeCompletionEvent)

Return type:

None

exception kailash.runtime.durable.WorkflowShapeDriftError(idempotency_key: str, stored_fingerprint: str, current_fingerprint: str)[source]

Bases: RuntimeError

Raised when an idempotency-key resume targets a different workflow.

The runtime persisted a checkpoint under idempotency_key=K for a workflow whose structural fingerprint was F1. The caller is now resuming under the same idempotency_key=K but supplying a workflow whose fingerprint is F2 != 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 by rules/zero-tolerance.md Rule 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 as git reset --hard (must verify clean tree) and MigrationManager.apply_downgrade (must pass force_downgrade=True).

Parameters:
  • idempotency_key (str)

  • stored_fingerprint (str)

  • current_fingerprint (str)

Return type:

None

__init__(idempotency_key: str, stored_fingerprint: str, current_fingerprint: str) None[source]
Parameters:
  • idempotency_key (str)

  • stored_fingerprint (str)

  • current_fingerprint (str)

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 parameters argument of build_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.

Parameters:

workflow (Any)

Return type:

str

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_id partition is mandatory per rules/tenant-isolation.md MUST Rule 5: a tenant MUST NOT be able to read another tenant’s checkpoint by guessing or replaying the same idempotency_key.

Parameters that are not JSON-serialisable degrade gracefully via default=str — the key remains stable for stable inputs.

Parameters:
  • workflow_fingerprint (str)

  • idempotency_key (str)

  • parameters (Mapping[str, Any] | None)

  • tenant_id (str | None)

Return type:

str

kailash.runtime.durable.redact_event_for_persistence(event: NodeCompletionEvent, *, classification_policy: Any | None = None) NodeCompletionEvent[source]

Return a copy of event safe to persist to checkpoint / history.

This is the shared redaction helper mandated by the cross-cutting invariant 3.1 in the architecture plan. Both LocalRuntime checkpoint emission AND the W2 history store path MUST route every event through this function before any store.save(...) / store.record(...) call.

Behavior:

  • If classification_policy is None (no classification configured), the event is returned unchanged. This matches the runtime’s default single-tenant single-classification posture.

  • If classification_policy is provided, this function consults the policy for each field of event.outputs AT EVERY DEPTH (recursive walk through nested Mapping and Sequence values) and:

    • drops any field tagged REDACT and replaces it with the sentinel "[REDACTED]" (NOT NoneNone is a valid unredacted value),

    • hashes any field tagged HASH_PK via format_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_path joined by . separators, e.g. "customer.ssn" for a top-level customer dict containing ssn, or "items.0.password" for the first element of a top-level items list whose dict carries password.

  • If the policy returns REDACT / HASH_PK for 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 REDACT exactly 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.md Rule 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.md MUST Rule 3) lives in the event’s metadata dict under metadata["classification_summary"] and contains:

  • unclassified_fields: list[str] — names safe to log

  • classified_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:
Return type:

NodeCompletionEvent

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_state with per-node outputs redacted.

The W2 runtime.on_node_complete hook 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_payload was previously called with tracker_state=execution_tracker.to_dict(), which embeds raw classified outputs in node_outputs[<node_id>].

Anyone using LocalRuntime(checkpoint_store=…, checkpoint_after_each_node=True) with a classification_policy was silently writing raw classified field values to disk — exactly the “fake redaction / fake classification” failure mode named by rules/zero-tolerance.md Rule 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 synthetic NodeCompletionEvent whose outputs carry the cached payload, run redact_event_for_persistence() over it, and write the redacted outputs back into a fresh tracker-state dict. Other tracker fields (completed_nodes ordering) are copied verbatim — they carry no classified content.

Behavior matches redact_event_for_persistence() exactly: when classification_policy is None the function returns a deep copy of tracker_state unchanged. When a policy is provided, every classified field in every per-node output dict is replaced with the "[REDACTED]" sentinel or hashed via HASH_PK semantics.

The function NEVER raises on a missing node_outputs key — 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 with node_outputs keyed by node_id).

  • classification_policy (Any | None) – Same policy object passed to redact_event_for_persistence(). When None, 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:

Dict[str, Any]

Notes

Known constraints. Synthetic events constructed by this helper use datetime.now(timezone.utc) as both started_at and ended_at because the per-node tracker state captured by ExecutionTracker does 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 real runtime.on_node_complete hook surface where the runtime supplies actual started_at / ended_at per 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_state is never mutated; a

  • deep-copy of node_outputs is taken so the caller’s tracker is

  • safe.

Parameters:
  • tracker_state (Mapping[str, Any])

  • classification_policy (Any | None)

  • workflow_id (str)

  • workflow_fingerprint (str)

  • tenant_id (str | None)

  • idempotency_key (str | None)

Return type:

Dict[str, Any]

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.

Parameters:
  • workflow_fingerprint (str)

  • tracker_state (Mapping[str, Any])

  • tenant_id (str | None)

  • workflow_id (str | None)

  • idempotency_key (str | None)

Return type:

bytes

kailash.runtime.durable.decode_checkpoint_payload(blob: bytes) Dict[str, Any][source]

Decode a blob produced by encode_checkpoint_payload().

Raises ValueError if the header is missing required fields. This is the runtime-layer typed-error gate per rules/zero-tolerance.md Rule 3a — opaque KeyError from payload["workflow_fingerprint"] is BLOCKED.

Parameters:

blob (bytes)

Return type:

Dict[str, Any]

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 WorkflowShapeDriftError for semantics.

Parameters:
  • idempotency_key (str)

  • stored_payload (Mapping[str, Any])

  • current_fingerprint (str)

  • force_resume_with_drift (bool)

Return type:

None

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:

  1. runtime.user_context.tenant_id if exposed

  2. kailash.trust.auth.context.get_current_tenant_id() if importable

  3. None (single-tenant deployments)

A missing trust subsystem (ImportError / AttributeError on the kailash.trust.auth.context surface) 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 as rules/zero-tolerance.md Rule 3 (no silent fallbacks): narrow the except clause to the documented “optional surface” path; let everything else propagate.

Parameters:

runtime (Any)

Return type:

str | None

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: object

First-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 plain AsyncLocalRuntime.

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_store is forwarded to AsyncLocalRuntime(checkpoint_store=..., checkpoint_after_each_node=True) so per-node blobs land via the W1 hot path (see kailash.runtime.local _record_node_completion).

  • history_store is forwarded to AsyncLocalRuntime(history_store=...) which auto-subscribes history_store.record_event against the hook registry at construction time. The engine does NOT call runtime.on_node_complete(history_store.record_event) separately — doing so would double-register the subscriber.

  • dispatcher is consulted only when configured AND execution_mode selects a dispatching path ("both" or "dispatch_only"). When set under "both", execute() enqueues a fire-time Task BEFORE running the workflow in-process; the enqueue and the in-process run race, and the structural defense is layered (dispatcher PRIMARY KEY idempotency on task_id prevents duplicate enqueue; W1 checkpoint resume short-circuits the second runner). Callers who need to eliminate the race entirely set execution_mode="in_process_only" (skip enqueue) or "dispatch_only" (skip the in-process runtime call). See execute() 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.

Parameters:
  • 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'])

Return type:

None

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_complete subscribers beyond the W2 history store auto-subscribe. The runtime instance is the SAME one engine.execute delegates to.

property history: Any

The composed history store, if configured. None otherwise.

Exposes the native WorkflowHistoryStore read API (list_runs, get_run, get_run_events, list_failed). Tenant scope is mandatory on every read per rules/tenant-isolation.md MUST Rule 5.

property checkpoint_store: Any

The composed checkpoint store, if configured. None otherwise.

property dispatcher: Any

The composed dispatcher, if configured. None otherwise.

property idempotency_key_default: str | None

The default idempotency key applied when execute omits 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 via DurableExecutionEngineBuilder.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:

DurableExecutionEngineBuilder

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_default when omitted. Forwarded to runtime.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 True an WorkflowShapeDriftError is suppressed and the engine proceeds against the new shape per W1 §4.6.4. The default is to refuse on drift, matching git reset --keep and MigrationManager.apply_downgrade(force_downgrade=True).

  • dispatch_kwargs (Mapping[str, Any] | None) – Free-form dict serialised into the dispatched Task kwargs field. 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 SoftTimeLimitExceeded when 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 HardTimeLimitExceeded after time_limit + grace regardless of soft-limit acknowledgement.

  • kwargs (Any)

Returns:

(results, run_id) from the wrapped runtime. The runtime’s run_id is also used to derive the dispatched task’s schedule_id so an operator can correlate the queue row with the history row.

Return type:

Tuple[Dict[str, Any], str]

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 Dispatcher whose enqueue catches 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 via engine.history.get_run(schedule_id, ...). The empty results dict 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_id PRIMARY KEY idempotency (Dispatcher MUST 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 subsequent runtime.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 SQLTaskQueueDispatcher worker + W1 checkpoint resume contain this; custom Dispatchers MUST honor the same idempotency_key resume contract or callers MUST switch to "in_process_only" / "dispatch_only" to eliminate the race entirely.

Parameters:
  • checkpoint_store (Optional[Any])

  • history_store (Optional[Any])

  • dispatcher (Optional[Any])

  • idempotency_key_default (Optional[str])

  • runtime_factory (Callable[..., Any])

  • runtime_kwargs (Optional[Mapping[str, Any]])

  • execution_mode (ExecutionMode)

class kailash.runtime.durable.DurableExecutionEngineBuilder[source]

Bases: object

Fluent builder for DurableExecutionEngine.

Build pattern:

  1. DurableExecutionEngine.builder() returns a fresh builder.

  2. Chain optional .checkpoint_store(store) / .history_store(store) / .dispatch_via(dispatcher) / .idempotency_key_default(key) / .runtime(factory) / .runtime_kwargs(mapping) calls.

  3. Call .build() to produce the immutable engine.

Each setter returns self so 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 is AsyncLocalRuntime — pass a custom factory only when a subclass is needed (Docker, custom timeout, alternative scheduler).

__init__() None[source]
Return type:

None

checkpoint_store(store: Any) DurableExecutionEngineBuilder[source]

Configure the checkpoint store. None clears the prior value.

Parameters:

store (Any)

Return type:

DurableExecutionEngineBuilder

history_store(store: Any) DurableExecutionEngineBuilder[source]

Configure the history store. None clears the prior value.

Parameters:

store (Any)

Return type:

DurableExecutionEngineBuilder

dispatch_via(dispatcher: Any) DurableExecutionEngineBuilder[source]

Configure the dispatcher. None clears the prior value.

Parameters:

dispatcher (Any)

Return type:

DurableExecutionEngineBuilder

idempotency_key_default(key: str | None) DurableExecutionEngineBuilder[source]

Set the default idempotency_key applied to execute calls.

Parameters:

key (str | None)

Return type:

DurableExecutionEngineBuilder

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 default AsyncLocalRuntime. Pass a custom callable when an AsyncLocalRuntime subclass or a compatible alternative is required.

Parameters:

runtime_factory (Callable[[...], Any] | None)

Return type:

DurableExecutionEngineBuilder

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) but execute() does not enqueue.

"dispatch_only"

Enqueue the task; do NOT invoke the wrapped runtime. execute() returns ({}, schedule_id). Requires a dispatcher — build() raises ValueError if 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 same idempotency_key resume contract. Requires a dispatcher — build() raises if absent.

Calling .execution_mode(None) (or omitting the call) defers to the auto-detect at build() 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:

DurableExecutionEngineBuilder

runtime_kwargs(kwargs: Mapping[str, Any]) DurableExecutionEngineBuilder[source]

Override base kwargs forwarded to the runtime factory.

checkpoint_store / checkpoint_after_each_node / history_store are added by build() AFTER these kwargs, so they always win over conflicting entries here. Use this for max_concurrent_nodes, execution_timeout, user_context, etc.

Parameters:

kwargs (Mapping[str, Any])

Return type:

DurableExecutionEngineBuilder

build() DurableExecutionEngine[source]

Construct the immutable DurableExecutionEngine.

Lazy-imports AsyncLocalRuntime as the default factory. The lazy import keeps kailash.runtime.durable importable when async_local is not yet available (cyclic-import safety) and matches the W1 / W2 module-load contracts.

Return type:

DurableExecutionEngine

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_id as the idempotency_key to runtime.execute(...) when paired with a checkpoint store. This enables resume-from-checkpoint semantics on crash recovery. See specs/scheduling.md for the full contract.

Module: kailash.runtime.dispatcher Added in: v0.13.x (issue #859)

class kailash.runtime.dispatcher.Dispatcher[source]

Bases: ABC

Abstract base class for workflow dispatchers.

A Dispatcher routes a fire-time Task to a queue (or other transport) so that one or more workers can poll, execute, and acknowledge. The contract is intentionally minimal: enqueue is idempotent on task_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:

  1. Make enqueue() idempotent on task_id – a duplicate enqueue with the same task_id MUST be a silent no-op (no exception to the caller).

  2. Make poll() atomic – two concurrent workers polling the same queue MUST NOT receive the same task.

  3. 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.

Parameters:

task (Task) – The task to enqueue.

Raises:

Exception – On any non-duplicate failure (connectivity, serialization). Callers (e.g. WorkflowScheduler.fire) MUST log this at ERROR with schedule_id + task_id and propagate or inline-retry per their misfire policy.

Return type:

None

abstractmethod poll(queue_name: str = 'default') AsyncIterator[Task][source]

Yield tasks claimed from the queue, one at a time.

Each yielded task is in processing status and locked to the polling worker. The worker MUST eventually call ack() (success) or nack() (failure) for each task to release the lock.

Parameters:

queue_name (str) – Queue to poll. Defaults to "default".

Returns:

Async iterator yielding claimed tasks. Implementations MAY block briefly between yields when the queue is empty, or return an async generator that completes once the worker is shut down.

Return type:

AsyncIterator[Task]

abstractmethod async ack(task_id: str) None[source]

Mark a task as completed.

Parameters:

task_id (str) – The task to ack.

Return type:

None

abstractmethod async nack(task_id: str, *, reason: str) None[source]

Mark a task as failed.

If the task has exceeded its max_attempts, the dispatcher MUST move it to dead-letter status. Otherwise, the dispatcher MUST requeue the task for another attempt.

Parameters:
  • task_id (str) – The task that failed.

  • reason (str) – A short error description for diagnostics. MUST NOT contain secrets or PII (callers’ responsibility per rules/security.md § “No secrets in logs”).

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: object

A scheduled workflow task ready for dispatch.

The Task is 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 deterministic task_id used 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 canonical idempotency_key workers SHOULD pass to runtime.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 via Workflow.from_dict(json.loads(...)) — NOT pickle.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 per rules/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:
task_id: str
schedule_id: str
workflow_blob: bytes
planned_fire_time: str
queue_name: str = 'default'
kwargs: Dict[str, Any]
__post_init__() None[source]

Validate queue_name against the canonical queue-name policy.

The scheduler-dispatcher path (#859) and the distributed-runtime path (#911) both carry a queue_name field. Pre-#911-Shard-2 only the distributed path validated; an invalid name in this Task could 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

__init__(task_id: str, schedule_id: str, workflow_blob: bytes, planned_fire_time: str, queue_name: str = 'default', kwargs: Dict[str, ~typing.Any]=<factory>) None
Parameters:
Return type:

None

kailash.runtime.dispatcher.compute_task_id(schedule_id: str, planned_fire_time: datetime) str[source]

Compute the stable, deterministic task_id for 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:
  • schedule_id (str) – The scheduler-assigned schedule identifier (e.g. “sched-abc123”).

  • planned_fire_time (datetime) – The cron/interval/once trigger’s planned fire time. MUST be the scheduler-computed fire time, NOT the wall-clock time when the callback ran (those drift under load).

Returns:

A 32-character lowercase hex string. Suitable for use as a PRIMARY KEY in the task queue table.

Return type:

str

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: object

Execution 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:

tuple[dict[str, Any], str]

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: object

Enhanced parallel runtime with support for cyclic workflows and concurrent execution.

Parameters:
__init__(debug: bool = False, max_workers: int = 4, enable_cycles: bool = True, enable_async: bool = True, runtime=None)[source]

Initialize the parallel cyclic runtime.

Parameters:
  • debug (bool) – Whether to enable debug logging

  • max_workers (int) – Maximum number of worker threads for parallel execution

  • enable_cycles (bool) – Whether to enable cyclic workflow support

  • enable_async (bool) – Whether to enable async execution features

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:

tuple[dict[str, Any], str | None]

close()[source]

Release runtime reference.

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: object

Parallel 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={…})

Parameters:
__init__(max_workers: int = 8, debug: bool = False)[source]

Initialize the parallel runtime.

Parameters:
  • max_workers (int) – Maximum number of concurrent node executions

  • debug (bool) – Whether to enable debug logging

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:

tuple[dict[str, Any], str | None]

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:

  1. CPU-Bound Tasks: Use for computationally intensive operations

  2. Data Parallelism: Split large datasets into chunks

  3. Resource Management: Monitor memory usage per worker

  4. 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: object

Docker-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:

tuple[dict[str, dict[str, Any]], str | None]

cleanup()[source]

Clean up Docker resources.

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

Runtime Feature 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")

See Also