Execution Engines¶
Hermes uses an abstraction layer to support multiple workflow execution engines. Currently, Luigi is the primary engine.
Engine Architecture¶
flowchart LR
TW["TaskWrapper\nNetwork"] --> Builder["Engine Builder"]
Builder --> Code["Generated Code"]
Code --> Engine["Execution Engine"]
subgraph Engines ["Available Engines"]
Luigi["Luigi\n(Active)"]
Airflow["Airflow\n(Planned)"]
end
Engine --> Luigi
Engine -.-> Airflow
Luigi Engine¶
Located in hermes/engines/luigi/, the Luigi engine consists of:
LuigiBuilder (builder.py)¶
The main builder that converts the TaskWrapper network into Luigi Python code:
- Generates imports — standard Luigi imports and Hermes utilities
- Iterates task wrappers — for each wrapper, finds the appropriate transformer
- Generates Luigi Task classes — each node becomes a Luigi
Tasksubclass - Handles dependencies — generates
requires()methods from the dependency graph - Generates output —
output()methods producingLocalTargetwith JSON
Task Generation Pattern¶
Each generated Luigi task follows this pattern:
class NodeName(hermesLuigiTask):
def requires(self):
return [DependencyNode1(), DependencyNode2()]
def output(self):
return luigi.LocalTarget("NodeName.json")
def run(self):
# Map parameters from dependencies
params = self._map_parameters(self.input())
# Invoke the node executer
result = executer.run(params)
# Write output
with self.output().open('w') as f:
json.dump(result, f)
Specialized Transformers¶
Some node types have specialized transformers that generate optimized Luigi code:
- OpenFOAM mesh nodes — handle mesh-specific execution patterns
- Workflow nodes — handle nested workflow execution
Task Utilities (taskUtils.py)¶
Common utilities shared across generated tasks:
- Parameter mapping from dependency outputs
- Path resolution and file handling
- Error handling and logging
Adding a New Engine¶
To add support for a new execution engine (e.g., Airflow):
- Create a new directory:
hermes/engines/airflow/ - Implement a builder class that:
- Accepts the TaskWrapper network
- Generates engine-specific code (e.g., Airflow DAG Python file)
- Maps task dependencies to the engine's dependency model
- Register the engine name in the workflow's
build()method
The key interface is:
class MyEngineBuilder:
def __init__(self, workflow):
self.workflow = workflow
def build(self):
"""Generate execution code for the engine."""
# Iterate self.workflow.taskRepresentations
# Generate engine-specific code
pass
Engine Selection¶
The engine is selected at build time:
Currently only Luigi is supported, but the architecture is designed for multiple engines.