Conversation
akashdw
force-pushed
the
ad/template-step-task-groups
branch
from
September 2, 2026 06:58
180cc1c to
63c9326
Compare
harph
approved these changes
Sep 3, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Pull Request type
./gradlew build --write-locksto refresh dependencies)Changes in this PR
StepType.TEMPLATEexists in the code, together with theTemplateStepplaceholder,TemplateInitiatorand theTEMPLATEparam sources, but it has no runtime implementation. This PR implements it. A job template can now register a list of steps, and a template step runs that list as one inline workflow instance. The step's outcome is the instance's outcome. This is similar to an Airflow TaskGroup. The step list is stored once in the job template registry instead of being copied into every workflow that uses it.Nothing changes for existing workflows. A template step only runs after a job template with a step list is registered.
Registering a template
This PR adds a new
stepsfield toJobTemplate.Definition. The existingPOST /api/v3/job-templatesendpoint registers it:stepsis required whenstep_typeistemplateand rejected for every other step type. A new@JobTemplateDefinitionConstrainton thedefinitionfield ofJobTemplateandJobTemplateCreateRequestvalidates the list on upsert with the same rules as a workflow step list: non-empty, unique step ids, every transition names a step in the list, and the step count limit. These return 400:The step list is read from the job template that the step names.
inherit_fromstill merges params and tags, but it does not merge steps. Merging step lists across templates needs a defined merge rule first and is out of scope. A registered step list can itself contain a template step, so templates can be nested.Using a template
A workflow declares a template step and supplies the inputs. This PR adds a
sub_type_versionfield toTemplateStep, so a workflow can pin a template version like a typed step does. Without it, the version comes from thejob_template_versionworkflow param and thendefault, the same as for typed steps.The template's registered params are merged into the step, the workflow's values are merged over them, and the result is evaluated before the inline instance starts. The evaluated values and all parent workflow params are passed as the inline instance's run params, so every step in the list can read them by name. A later step in the parent workflow reads
publish__snapshot_idas it does for any other step.Runtime
TemplateStepRuntimeis structured like the other runtimes that launch child instances. The runtime owns the artifact, the run params it passes down, and the mapping from the child instance status to the step state. A newWorkflowActionHandler.runTemplateInstancemethod creates the child instance. The step lifecycle is the same as the subworkflow step: start one instance, track it, map its terminal status to a step state, and stop it on terminate. The child instance is created directly from a workflow built out of the registered steps, without run strategy and without a workflow definition lookup, because the step list is registry data and not a registered workflow.Each parent instance and template step has one inline workflow id, generated by the existing
StepHelper.generateInlineWorkflowIdwith a newmaestro_template_prefix. The child instance id is always 1 and its run id increases by one on each restart. Restarting the parent or the template step loads the previous run of the same child, applies the parent's run policy, and starts the next run.internal_idandworkflow_version_idon the child row are the parent's values, the same as foreach and while.A new
TemplateArtifactrecords the child instance (template_workflow_id,template_instance_id,template_run_id,template_uuid,template_overview) plus thejob_typeand thetemplate_versionresolved at start. A job template version can be updated in place, so the artifact records which version key was used and the child instance'sruntime_workflowholds the steps that actually ran.Terminate works the same as for a subworkflow step: a queued child instance is terminated directly, otherwise the child's actor is woken up and the terminate is retried until the child is in a terminal state.
Reading a param from a step in the template
params.getFromTemplate('<template_step_id>', '<step_id>', '<param>')is added next togetFromSubworkflowandgetFromForeach. It checks that the referenced step is a template step, reads the child workflow id and instance id from the artifact, and returns the named param from that step's latest run. It is documented in the SEL function guide.Inline id recognition and deletion
IdHelper.isInlineWorkflowIdnow recognizes the template prefix, sogetNonInlineParentreturns the correct parent for a template child. Both inline deletion stages inMaestroWorkflowDeletionDaoget a third id range, so deleting a workflow also deletes its template instances and their step instances. The new range isworkflow_id >= prefix AND workflow_id < prefix || '~'on the primary key, the same as the foreach and while ranges.MaestroWorkflowDao.getInlineWorkflowDefinitionInternalcast the child's initiator toForeachInitiator. That throwsClassCastExceptionfor any non-foreach inline child, including while children today. It now casts toUpstreamInitiator, wheregetNonInlineParentis declared. The delete-rejection message for an inline id no longer says "foreach".Ids starting with
maestro_are already rejected byMaestroIdConstraintandMaestroReferenceIdConstraint, so no workflow can be registered under the template prefix. A test asserts this for the template prefix.Tests
Every new model has round-trip and from-JSON tests with fixtures. The constraint test covers each rejection case. The runtime test covers fresh start, restart with and without a previous artifact, retry when the insert fails, instance step concurrency unavailable, a missing template, every child status mapping, and the terminate paths. The handler test covers fresh and restart runs.
JobTemplateManagertests coverloadSteps(found, missing, step type mismatch, no steps) and version resolution from a template step.MaestroParamExtensiontests covergetFromTemplateand its failure cases. DAO tests cover the inline definition lookup for a template child, the latest template artifact, and that a template child instance and its step row are deleted with the parent workflow.