- All Implemented Interfaces:
WorkflowRuntime
WorkflowRuntime implementation.
Owns the outer drive loop: resolves the workflow, creates or retrieves a
WorkflowState, delegates body execution to StepSequenceExecutor, reacts to pauses and completions by
updating the state and emitting events, and persists state through the injected repository after each drive.
Not thread-safe per run — a single run is driven by the caller's thread at any given time. The embedding application is responsible for preventing concurrent drives of the same run.
Construction is package-private and goes through WorkflowRuntimeBuilder. The
constructors take internal collaborators (StepSequenceExecutor, ExecutableExecutor) from the
non-exported com.agentforge4j.runtime.execution package, so they must not be part of the exported public
API.
-
Field Summary
FieldsModifier and TypeFieldDescriptionstatic final intDefault maximum workflow nesting depth — guards against pathological configs. -
Method Summary
Modifier and TypeMethodDescriptionvoidProvide human approval for a step that requires it.voidCancel the given run.continueAfterToolApproval(String runId, String toolInvocationId, ApprovalDecision decision) Resume a run suspended awaiting approval of a tool invocation, applying the human decision.voidcontinueRun(String runId, String actorId) Advance a paused run.voiddecideStepApproval(String runId, String stepId, StepApprovalDecision decision) Decide a step suspended inWorkflowStatus.AWAITING_STEP_APPROVALby aHUMAN_APPROVALgate.resolveToolDecision(String runId, String toolInvocationId, ToolDecision decision) Resolve a run suspended inWorkflowStatus.AWAITING_TOOL_DECISIONafter a tool invocation was denied by policy or failed after retries, applying the operator's decision.voidRetry the given step on the given run.Start a new run of the given workflow.voidSubmit answers to the pending artifact on a run inAWAITING_INPUTstatus.voidsubmitReview(String runId, String stepId, String reviewNote, String actorId) Submit a forward-only review for a step suspended inWorkflowStatus.AWAITING_REVIEWby aHUMAN_REVIEWgate.
-
Field Details
-
DEFAULT_MAX_NESTING_DEPTH
public static final int DEFAULT_MAX_NESTING_DEPTHDefault maximum workflow nesting depth — guards against pathological configs.- See Also:
-
-
Method Details
-
start
Start a new run of the given workflow.- Specified by:
startin interfaceWorkflowRuntime- Parameters:
workflowId- id of a workflow known to theWorkflowRepository- Returns:
- the newly-created run id
- Throws:
IllegalArgumentException- ifworkflowIdis blank
-
continueRun
Advance a paused run. Valid when the run is inPAUSEDstatus.- Specified by:
continueRunin interfaceWorkflowRuntime- Parameters:
runId- id of the run to advanceactorId- Opaque identifier supplied by the embedding application representing the entity responsible for the action. AgentForge4j treats the value as opaque and does not interpret its structure or meaning.- Throws:
ExecutionNotFoundException- if no state exists forrunIdIllegalArgumentException- if the run is cancelled, notWorkflowStatus.PAUSED, orrunIdis blank
-
retry
Retry the given step on the given run. Honours the step'sRetryPolicy: an AGENT or SPAR target is retried only when its policy'sallowRetryistrueand the number of attempts already made against that step's shared attempt budget — shared between this verb and anyRETRY_PREVIOUSstep targeting the same step — is still under the policy'smaxAttemptsceiling; a granted retry consumes one unit of that budget. An AGENT/SPAR step that declares no policy defaults toRetryPolicy.none()and is therefore not retryable at all through this verb (fail-closed). A step type with noRetryPolicyconcept (anything other than AGENT/SPAR) is unrestricted. A rejected retry throwsIllegalStateExceptionand leaves the run untouched.stepIdmust name a top-level step — one that appears directly in the workflow's top-level sequence. The run is repositioned at that step and the sequence is re-driven, so the target and every step after it execute again and the run finalises on the real downstream outcome (it may complete, pause, or fail). A step that exists only nested inside a blueprint or sub-workflow is rejected; retry its enclosing top-level step instead.- Specified by:
retryin interfaceWorkflowRuntime- Parameters:
runId- id of the runstepId- id of the top-level step to retryactorId- Opaque identifier supplied by the embedding application representing the entity responsible for the action. AgentForge4j treats the value as opaque and does not interpret its structure or meaning.- Throws:
ExecutionNotFoundException- if no state exists forrunIdIllegalArgumentException- ifstepIdis blank, the run is cancelled, status is notWorkflowStatus.FAILEDorWorkflowStatus.PAUSED,stepIddoes not identify a top-level step in the workflow definition, orrunIdis blankIllegalStateException- if the target'sRetryPolicyforbids operator retry (allowRetry=false— including the undeclared-policyRetryPolicy.none()default on an AGENT/SPAR step), or the sharedmaxAttemptsceiling (shared withRETRY_PREVIOUSsteps targeting the same step) is already reached; the run is left untouched- Implementation Requirements:
retryis a top-level-step contract:stepIdmust name a step that appears directly in the workflow's top-level sequence. The run is repositioned at that step and the enclosing sequence is re-driven, so the target and every downstream step execute again and the run finalises on the real downstream outcome. A step that exists only nested inside a blueprint or sub-workflow is rejected (retry its enclosing top-level step instead).
-
approve
Provide human approval for a step that requires it.- Specified by:
approvein interfaceWorkflowRuntime- Parameters:
runId- id of the runstepId- id of the approved stepapproverNote- human-readable note recorded in the event logactorId- Opaque identifier supplied by the embedding application representing the entity responsible for the action. AgentForge4j treats the value as opaque and does not interpret its structure or meaning.- Throws:
ExecutionNotFoundException- if no state exists forrunIdIllegalArgumentException- ifstepIdis blank, the run is cancelled, status is notWorkflowStatus.AWAITING_APPROVAL, orrunIdis blankIllegalStateException- ifstepIddoes not identify the step the run is suspended on
-
submitInput
Submit answers to the pending artifact on a run inAWAITING_INPUTstatus.Keys are artifact item ids; values are the raw answers. The runtime writes each answer to the shared context under the namespaced key
artifactId.itemId. Additionally, when an answer's item id is one of the current INPUT step's declared output keys, the value is also written under that bare key, so downstream steps and branches that read the declared output key resolve it.- Specified by:
submitInputin interfaceWorkflowRuntime- Parameters:
runId- id of the runanswers- map of artifact item id to user-provided answeractorId- Opaque identifier supplied by the embedding application representing the entity responsible for the action. AgentForge4j treats the value as opaque and does not interpret its structure or meaning.- Throws:
ExecutionNotFoundException- if no state exists forrunIdIllegalArgumentException- ifanswersisnull, the run is cancelled, status is notWorkflowStatus.AWAITING_INPUT, a pending artifact path is inconsistent with state, orrunIdis blank
-
getState
- Specified by:
getStatein interfaceWorkflowRuntime- Parameters:
runId- id of the run- Returns:
- a defensive snapshot of the current state — mutating the returned object does not alter persisted runtime state
- Throws:
ExecutionNotFoundException- if no state exists forrunIdIllegalArgumentException- ifrunIdis blank
-
continueAfterToolApproval
public WorkflowState continueAfterToolApproval(String runId, String toolInvocationId, ApprovalDecision decision) Resume a run suspended awaiting approval of a tool invocation, applying the human decision.Distinct from
WorkflowRuntime.approve(String, String, String, String)(which resumes a step and may re-invoke the agent): this delegates to the tool-execution service's resume path — re-resolving and invoking the exact approved tool without re-invoking the LLM — and applies the outcome to state. An approved invocation that executes successfully, or a rejected one, advances past the requesting step. An approved invocation whose call fails (resolution, validation, or the provider itself) does not advance: a fresh pending row (originEXECUTION_FAILED) is persisted and the run re-suspends inWorkflowStatus.AWAITING_TOOL_DECISION, giving the operator a further decision point viaWorkflowRuntime.resolveToolDecision(java.lang.String, java.lang.String, com.agentforge4j.core.spi.tool.ToolDecision). Valid only when the run is inWorkflowStatus.AWAITING_TOOL_APPROVAL.An
ApprovalDecision.Approvewhose resumed call succeeds applies the tool result and advances the requesting step; anApprovalDecision.Rejectrecords the tool error and likewise advances. An approved call that fails (resolution, validation, or the provider itself) does not advance: the tool-execution service has already persisted a fresh pending row (originEXECUTION_FAILED), and this method re-suspends the run inWorkflowStatus.AWAITING_TOOL_DECISIONso the operator gets that further decision point viaresolveToolDecisioninstead of the row being orphaned against an advanced run.- Specified by:
continueAfterToolApprovalin interfaceWorkflowRuntime- Parameters:
runId- id of the runtoolInvocationId- id of the pending tool invocation being decideddecision- the human approve/reject decision- Returns:
- a snapshot of the resumed run state
- Throws:
ExecutionNotFoundException- if no state exists forrunIdIllegalStateException- if no tool-execution service is configured, or the run is inWorkflowStatus.AWAITING_APPROVAL(useapprove) orWorkflowStatus.AWAITING_TOOL_DECISION(useresolveToolDecision)IllegalArgumentException- if the run is cancelled, notWorkflowStatus.AWAITING_TOOL_APPROVAL, or an id/argument is blank or nullPolicyDenialTerminalException- ifdecisionis anApprovalDecision.Approveagainst a policy-denied pending invocation (a denial is terminal; seeToolExecutionService.resume)ToolInvocationClaimLostException- if there is no claimable pending invocation for this id — whether because a concurrent resolution already claimed and resolved it before this call's own peek, or because a concurrent resume claimed or replaced it between this call's peek and its own claim attempt; no run state is mutated when this is thrown
-
resolveToolDecision
public WorkflowState resolveToolDecision(String runId, String toolInvocationId, ToolDecision decision) Resolve a run suspended inWorkflowStatus.AWAITING_TOOL_DECISIONafter a tool invocation was denied by policy or failed after retries, applying the operator's decision.ToolDecision.Continueproceeds without the tool result, writingtool.<capability>.errorto context and advancing past the requesting step.ToolDecision.Retryreplays the exact stored call without re-invoking the LLM: a successful replay applies the tool result and advances the requesting step, but a replay that fails again does not advance — a fresh pending row (originEXECUTION_FAILED) is persisted and the run re-suspends inAWAITING_TOOL_DECISIONfor a further decision.Retryagainst a pending invocation that was denied by policy is invalid: a denial is terminal for that invocation, so the call throws without invoking the provider or mutating run state, leaving the pending row intact for a laterToolDecision.Continue. Valid only when the run is inAWAITING_TOOL_DECISION; useWorkflowRuntime.approve(String, String, String, String)for escalation approvals instead.ToolDecision.Retryagainst a pending invocation that aToolPolicydenied is an invalid, non-executing transition: a policy denial is terminal for that invocation, so the underlying resume propagates aPolicyDenialTerminalExceptionwithout invoking the provider or mutating run state, leaving the run suspended inWorkflowStatus.AWAITING_TOOL_DECISIONwith its pending row intact for a laterToolDecision.Continue. A retried call that fails again re-suspends the run inWorkflowStatus.AWAITING_TOOL_DECISIONfor a further decision, rather than silently advancing without a tool result.- Specified by:
resolveToolDecisionin interfaceWorkflowRuntime- Parameters:
runId- id of the runtoolInvocationId- id of the pending tool invocation being resolveddecision- the operator continue/retry decision- Returns:
- a snapshot of the resumed run state
- Throws:
ExecutionNotFoundException- if no state exists forrunIdIllegalStateException- if no tool-execution service is configured, or the run is inWorkflowStatus.AWAITING_APPROVAL(useapproveinstead)IllegalArgumentException- if the run is cancelled, notWorkflowStatus.AWAITING_TOOL_DECISION, or an id is blank or the decision nullPolicyDenialTerminalException- ifdecisionis aToolDecision.Retryagainst a policy-denied pending invocationToolInvocationClaimLostException- if there is no claimable pending invocation for this id — whether because it was already claimed and resolved by a concurrent resolution before this call's own peek, or because a concurrent resolution claimed or replaced it between this call's peek and its own claim attempt. ForToolDecision.Retrythe latter is detected insidetoolExecutionService.resume; forToolDecision.Continuethis method claims the row itself before applying anything. No run state is mutated when this is thrown
-
cancel
Cancel the given run.- Specified by:
cancelin interfaceWorkflowRuntime- Parameters:
runId- id of the runactorId- Opaque identifier supplied by the embedding application representing the entity responsible for the action. AgentForge4j treats the value as opaque and does not interpret its structure or meaning.- Throws:
ExecutionNotFoundException- if no state exists forrunIdIllegalArgumentException- if status isWorkflowStatus.COMPLETEDorWorkflowStatus.FAILED, orrunIdis blank
-
submitReview
Submit a forward-only review for a step suspended inWorkflowStatus.AWAITING_REVIEWby aHUMAN_REVIEWgate. Records the note and advances the run; this is not an approve/reject.- Specified by:
submitReviewin interfaceWorkflowRuntime- Parameters:
runId- id of the runstepId- id of the reviewed stepreviewNote- human-readable note recorded in the event log; may be blankactorId- Opaque identifier supplied by the embedding application representing the entity responsible for the action. AgentForge4j treats the value as opaque and does not interpret its structure or meaning.- Throws:
ExecutionNotFoundException- if no state exists forrunIdIllegalArgumentException- ifstepId/actorIdis blank, the run is cancelled, or the run is not inAWAITING_REVIEWIllegalStateException- if the run is in a different suspension status
-
decideStepApproval
Decide a step suspended inWorkflowStatus.AWAITING_STEP_APPROVALby aHUMAN_APPROVALgate.StepApprovalDecision.Approveadvances the run;StepApprovalDecision.Rejectfails it (no send-back). The actor is carried inside the decision.- Specified by:
decideStepApprovalin interfaceWorkflowRuntime- Parameters:
runId- id of the runstepId- id of the step being decideddecision- the approve/reject decision- Throws:
ExecutionNotFoundException- if no state exists forrunIdIllegalArgumentException- ifstepIdis blank,decisionis null, the run is cancelled, or the run is not inAWAITING_STEP_APPROVALIllegalStateException- if the run is in a different suspension status
-