Developing runners
This page shows how to turn business logic into an executable runner. The example is a minimal runner called generic-sum-csv. It receives a CSV file, sums every numeric column, and returns a JSON object of the form:
{
"speed": 120.7,
"altitude": 3412,
"temperature": null
}Columns that cannot be coerced into numbers are reported as null.
Runtime contract
Every runner is a container that:
- Installs the
heat-runtimePython package. - Subclasses
BaseProcessorand implements two async hooks:validate()andprocess(). - Registers itself with
HeatRuntime.register()and starts the event loop withHeatRuntime().init(). - Remains idempotent, process a single task using only the provided inputs + config.
The typical event loop for the HEAT runtime polls the platform for tasks. When a task is available it is claimed so no other runner can process it. Your logic runs, then results are posted back through the runtime. You do not call HEAT HTTP APIs directly from runner code.
Implementation
from io import BytesIO
from typing import Dict, Any
import pandas as pd
from heat_runtime import BaseProcessor, HeatRuntime
class SumProcessor(BaseProcessor):
"""Aggregates all numeric columns of a CSV (supplied in‑memory) into a single JSON row."""
TEMPLATE_NAME = "generic-sum-csv" # must match the node template
async def validate(self, inputs: Dict[str, Any], config: Dict[str, Any]):
await super().validate(inputs, config)
if "file" not in inputs:
raise ValueError("expected 'file' artefact in inputs")
async def process(self, inputs: Dict[str, Any], config: Dict[str, Any]):
"""Sum every numeric column; non‑numeric become null."""
raw = inputs["file"] # bytes or file‑like object provided by HEAT
# normalise to a file‑like buffer that pandas can read
if isinstance(raw, (bytes, bytearray)):
buffer = BytesIO(raw)
else:
buffer = raw # assume already file‑like (e.g. StreamingBody)
df = pd.read_csv(buffer)
result = {}
for col in df.columns:
if pd.api.types.is_numeric_dtype(df[col]):
result[col] = float(df[col].sum())
else:
result[col] = None
return result
if __name__ == "__main__":
HeatRuntime.register(SumProcessor)
HeatRuntime().init()Why async?
heat-runtimehandles polling and heartbeat in the event loop. Your CPU-bound work can stay synchronous insideprocess().
Dockerfile
FROM python:3.12-slim
WORKDIR /app
# Install runtime and dependencies
RUN pip install heat-runtime pandas==2.*
# Copy source
COPY src/ ./src/
# Entrypoint
CMD ["python", "src/sum_processor.py"]Push this image to your registry and reference it in runnerTypes.containerImage.
Updating the manifest
Add a new runner type and node template:
{
"runnerTypes": [
{
"name": "sum-csv",
"containerImage": "registry.example.com/sum-csv:1.0.0",
"cpuLimit": "1",
"memoryLimit": "1Gi",
"description": "Sums numeric columns of a CSV.",
"enabled": true,
"featureInternalName": "myFeature",
"deployment": {
"minimumPods": 1,
"maximumPods": 2,
"cpuRequest": "500m"
}
}
],
"nodeTemplates": [
{
"name": "generic-sum-csv",
"type": "Processing",
"acceptsMultipleInputs": false,
"requiresConfiguration": false,
"configurationSchema": {},
"featureInternalName": "myFeature",
"supportedRunners": ["sum-csv"],
"hasNonJsonArtifacts": false
}
]
}Upload the updated manifest on the Features screen of HEAT Control Centre (formerly Cluster Manager). You can then reference the node template in session templates. The platform provisions runner workloads for generic-sum-csv tasks when sessions need them.
The optional deployment block shown on the runner type is a hint to the scheduler, not a requirement. minimumPods and maximumPods bound the replica range (the scheduler scales between them with task pressure; defaults are 2 and 4), and cpuRequest / memoryRequest set the Kubernetes requests instead of the default half of each limit. Leave the block out and the platform behaves exactly as before; an operator can still override any value per environment with the scheduler.limits.{runnerName}.* platform configuration keys.
Test locally (optional)
You can exercise the processor outside HEAT for quick feedback:
python src/sum_processor.pyWhen no environment flags indicate the HEAT cluster, the Python runtime runs in local dev mode for quick iteration.
Next steps
- Session templates: wire
generic-sum-csvinto a graph. - Features: upload and manage
.heatmanifests in Control Centre.
A field-by-field manifest reference and a session-template walkthrough for this example are not available today. Use the manifest skeleton on the introduction page and the schema linked from its $schema property.