Schedule agentic workflows
Agentic LangChain4j workflows usually run on demand, but many real-world AI processes need to be triggered automatically. Combined with tools, this lets an agent wake up on a schedule or event and then act on its own, calling tools to fetch data, take decisions, and produce results without any manual interaction.
Quarkus Flow lets you declare a trigger directly on the agentic method with the @ScheduleOn annotation. The engine then generates a schedulable workflow that fires whenever the configured trigger occurs.
|
To understand how schedulable workflows are generated and how they relate to the underlying agentic workflows, see Schedulable workflows in the concepts documentation. |
This guide shows how to:
-
Trigger an agentic workflow from a CloudEvent.
-
Send the event that triggers the workflow, from Microprofile Reactive Messaging connector or from
EventPublisher. -
Trigger it on a recurring CRON schedule.
-
Trigger it periodically with a fixed every.
-
Understand why only one trigger can be configured per method.
Prerequisites
-
A Quarkus application with LangChain4j agents set up.
-
An agentic method annotated with one of the LangChain4j declarative agent annotations (
@SequenceAgent,@ParallelAgent,@LoopAgent, or@ConditionalAgent).
The @ScheduleOn annotation
@ScheduleOn is placed on the agentic method and exposes three mutually exclusive trigger strategies:
| Member | Type | Triggers the workflow when… |
|---|---|---|
|
CloudEvent type ( |
an event of the given type is received. |
|
Unix cron expression ( |
the cron schedule elapses. |
|
ISO 8601 duration ( |
the fixed period elapses. |
|
You must configure exactly one strategy per method. If none of |
Only the event strategy can pass input to the workflow:
-
event— the CloudEvent payload becomes the workflow input, so the agentic method takes the event data as a parameter. -
cronandevery— these triggers fire on a timer and carry no payload, so they cannot provide any input. The agentic method must be parameterless.
|
Because cron and every triggers have no payload, annotating a method that declares parameters with a
|
1. Trigger from a CloudEvent
Use event to start the workflow whenever a CloudEvent of the given type is received.
@RegisterAiService
public interface ConferenceReviewerPlanner {
@ScheduleOn(event = "proposal.submitted")
@ParallelAgent(outputKey = "reviewResult",
subAgents = { ProposalImproverAgent.class, ScoreJavaProposal.class })
ProposalReview proposalReview(@V("proposal") Proposal proposal);
}
Every time a proposal.submitted CloudEvent arrives, the agentic workflow runs with the event payload as its input.
How the CloudEvent payload maps to method parameters
The CloudEvent data is deserialized into the agentic method’s parameters:
-
Single parameter — the entire payload becomes that parameter.
-
Multiple parameters — the payload must be a JSON object whose field names match the parameter names; each parameter is bound from its matching field.
Example with single parameter:
@ScheduleOn(event = "proposal.submitted")
@ParallelAgent(outputKey = "reviewResult",
subAgents = { ProposalImproverAgent.class, ScoreJavaProposal.class })
ProposalReview proposalReview(@V("proposal") Proposal proposal);
CloudEvent payload (entire JSON becomes the proposal parameter):
{
"id": 1,
"title": "Quarkus Flow, Java and AI Orchestration",
"description": "It is a great talk (trust)",
"subject": "Quarkus Flow"
}
Example with multiple parameters:
@ScheduleOn(event = "review.requested")
@SequenceAgent(outputKey = "result",
subAgents = { ReviewerAgent.class })
ReviewResult review(@V("proposalId") Long proposalId,
@V("reviewerName") String reviewerName);
CloudEvent payload (fields map to parameter names):
{
"proposalId": 42,
"reviewerName": "Jane Doe"
}
The proposalId field maps to the proposalId parameter, and reviewerName maps to the reviewerName parameter.
Sending the event that triggers the workflow
An event trigger only fires when a CloudEvent of the configured type reaches the engine.
Microprofile Reactive Messaging Bridge (recommended)
The most common way is to send it from an external system through the messaging bridge: publish a CloudEvent
to the inbound flow-in channel and the engine matches its type against your
@ScheduleOn(event = "…"). See Use messaging and events for more details.
Using WorkflowApplication publishers
You can also publish the triggering event programmatically through the engine’s EventPublisher,
which is handy in tests or when the producer lives in the same application. Obtain a publisher from
the WorkflowApplication and build a CloudEvent whose type matches the @ScheduleOn(event = "…")
value:
@Inject
WorkflowApplication workflowApp;
@Inject
ObjectMapper objectMapper;
void triggerReview() {
EventPublisher publisher = workflowApp.eventPublishers().iterator().next();
JsonNode data = objectMapper.convertValue(Map.of(
"id", 1L,
"title", "Quarkus Flow, Java and AI Orchestration",
"description", "It is a great talk (trust)",
"subject", "Quarkus Flow, Java and AI Orchestration"), JsonNode.class);
publisher.publish(new CloudEventBuilder()
.withId(UUID.randomUUID().toString())
.withSource(URI.create("http://localhost/conference-system"))
.withType("proposal.submitted") (1)
.withData(JsonCloudEventData.wrap(data))
.build());
}
| 1 | The CloudEvent type must match the @ScheduleOn(event = "proposal.submitted") declared on the
agentic method. |
Once published, the engine correlates the event to the workflow and
runs it with the event data as input.
2. Trigger on a CRON schedule
Use cron for time-based, calendar-like scheduling. The expression follows the standard Unix cron format.
@ScheduleOn(cron = "0 0 * * *") // every day at midnight
@SequenceAgent(outputKey = "digest", subAgents = { MessageSummaryAgent.class })
String dailyDigest();
|
Unix cron has whole-minute granularity. For sub-minute cadences use |
3. Trigger on a fixed every
Use every to run the workflow periodically. The value is an ISO 8601 duration string,
for example PT5M (every 5 minutes) or PT1H (every hour).
@ScheduleOn(every = "PT1H") // every hour
@SequenceAgent(outputKey = "digest", subAgents = { MessageSummaryAgent.class })
String hourlyDigest();