Tell an execution's page which loops it runs
An execution's view carried each earlier round's steps but not whose rounds they were: nodes do not carry their capabilities, so a page could not tell a loop's guard from any other step. The view now lists the loops the engine runs - guard, entry, the output that goes round, the limit and the steps of each - the same after a reload as before it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
6cb62c22ea
commit
a263dd5944
|
|
@ -340,6 +340,11 @@ public class ExecutionContext implements ExecutionListener {
|
|||
loops.forEach(loop -> this.loopsByGuard.put(loop.loop().guardId(), loop));
|
||||
}
|
||||
|
||||
/** The loops this execution may go round, in no particular order. */
|
||||
public List<LoopRuntime> getLoops() {
|
||||
return List.copyOf(this.loopsByGuard.values());
|
||||
}
|
||||
|
||||
private boolean isInLoop(String stepId) {
|
||||
return this.loopsByGuard.values().stream().anyMatch(loop -> loop.loop().contains(stepId));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,6 +39,8 @@ public class ExecutionContextView {
|
|||
private Map<String, ExecutionStepView> steps;
|
||||
/** Loop steps as each earlier round left them, oldest first; the steps above are the current round. */
|
||||
private java.util.List<ExecutionStepSnapshot> stepHistory;
|
||||
/** The loops the execution may go round, so a page can tell whose rounds those are. */
|
||||
private java.util.List<ExecutionLoopView> loops;
|
||||
private ExecutionStatus status;
|
||||
private java.util.List<String> waitingSteps;
|
||||
|
||||
|
|
@ -66,6 +68,7 @@ public class ExecutionContextView {
|
|||
(left, right) -> right,
|
||||
java.util.LinkedHashMap::new)))
|
||||
.stepHistory(context.getStepHistory())
|
||||
.loops(context.getLoops().stream().map(ExecutionLoopView::of).toList())
|
||||
.status(context.getStatus())
|
||||
.waitingSteps(context.getWaitingSteps())
|
||||
.build();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
// SPDX-FileCopyrightText: 2025-2026 Lucio Lelii <lucio.lelii@isti.cnr.it> - ISTI-CNR
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
// Attribution term under AGPL-3.0 section 7(b): see LICENSE-ADDENDUM.
|
||||
|
||||
package it.cnr.isti.workflow.manager.executions.api;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import it.cnr.isti.workflow.manager.executions.LoopRuntime;
|
||||
|
||||
/**
|
||||
* A loop of a running flow, as the engine runs it.
|
||||
*
|
||||
* @param guardId the step that decides each time whether to go round again
|
||||
* @param entryId where each new round starts
|
||||
* @param backOutput the guard's output that goes round; any other one leaves the loop
|
||||
* @param maxIterations how many rounds are allowed before the execution fails
|
||||
* @param memberIds every step that runs again on each round
|
||||
*/
|
||||
public record ExecutionLoopView(String guardId, String entryId, String backOutput, int maxIterations,
|
||||
List<String> memberIds) {
|
||||
|
||||
public static ExecutionLoopView of(LoopRuntime runtime) {
|
||||
return new ExecutionLoopView(runtime.loop().guardId(), runtime.loop().entryId(), runtime.backOutput(),
|
||||
runtime.loop().maxIterations(), runtime.loop().memberIds().stream().sorted().toList());
|
||||
}
|
||||
}
|
||||
|
|
@ -38,6 +38,7 @@ import it.cnr.isti.workflow.manager.blocks.types.ConditionalBlockType;
|
|||
import it.cnr.isti.workflow.manager.blocks.types.EndBlockType;
|
||||
import it.cnr.isti.workflow.manager.blocks.types.HumanDecisionBlockType;
|
||||
import it.cnr.isti.workflow.manager.blocks.types.LLMBlockType;
|
||||
import it.cnr.isti.workflow.manager.executions.api.ExecutionContextView;
|
||||
import it.cnr.isti.workflow.manager.executions.persistence.ExecutionStepSnapshot;
|
||||
import it.cnr.isti.workflow.manager.executions.steps.StepStatus;
|
||||
import it.cnr.isti.workflow.manager.flows.model.Connection;
|
||||
|
|
@ -166,6 +167,15 @@ class LoopExecutionTest {
|
|||
.findFirst().orElseThrow().getValue())
|
||||
.toList());
|
||||
|
||||
ExecutionContextView view = ExecutionContextView.fromContext(execution.getContext());
|
||||
assertEquals(1, view.getLoops().size(), "the page is told which loop those rounds belong to");
|
||||
assertEquals(loop.check().getId(), view.getLoops().getFirst().guardId());
|
||||
assertEquals(loop.draft().getId(), view.getLoops().getFirst().entryId());
|
||||
assertEquals(ConditionalBlockFactory.FALSE_OUTPUT, view.getLoops().getFirst().backOutput());
|
||||
assertEquals(10, view.getLoops().getFirst().maxIterations());
|
||||
assertEquals(java.util.Set.of(loop.draft().getId(), loop.check().getId()),
|
||||
java.util.Set.copyOf(view.getLoops().getFirst().memberIds()));
|
||||
|
||||
List<ExecutionEvent> rounds = execution.getContext().getEvents().stream()
|
||||
.filter(event -> event.getType() == ExecutionEventType.LOOP_ITERATION_STARTED)
|
||||
.toList();
|
||||
|
|
@ -255,6 +265,8 @@ class LoopExecutionTest {
|
|||
ExecutionObject reloaded = executionsService.getExecution(execution.getId());
|
||||
assertEquals(3, reloaded.getContext().getSteps().get(draft.getId()).getIteration());
|
||||
assertEquals(4, reloaded.getContext().getStepHistory().size(), "two rounds of two steps each");
|
||||
assertEquals(List.of(review.getId()), reloaded.getContext().getLoops().stream()
|
||||
.map(runtime -> runtime.loop().guardId()).toList(), "the loop is known again after the reload");
|
||||
executionsService.resumeExecution(execution.getId());
|
||||
|
||||
executionsService.setInteractionValue(execution.getId(), review.getId(),
|
||||
|
|
|
|||
Loading…
Reference in New Issue