DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Related

  • The Future of Java and AI: Coding in 2025
  • GenAI in Java With Merlinite, Quarkus, and Podman Desktop AI Lab
  • How to Build Living AI Coding Assistants With Quarkus Agent MCP
  • AGENTS.md Makes Your Java Codebase AI-Agent Ready

Trending

  • Machine Identity Debt: Why Human Identity Is No Longer Cloud Security's Primary Boundary
  • Connect to RabbitMQ Using Scala, Play and Akka
  • Show Heap Status in Eclipse
  • How to Build a Solid Test Pipeline in the Era of Agentic AI Development
  1. DZone
  2. Data Engineering
  3. AI/ML
  4. Orchestration Meets MCP: Building Governed Agentic Workflows With Quarkus Flow and AGENTS.md

Orchestration Meets MCP: Building Governed Agentic Workflows With Quarkus Flow and AGENTS.md

By combining Quarkus Flow, LangChain4j, MCP tools, and AGENTS.md, developers can construct deterministic, tool-augmented, and enterprise-governed AI agent loops.

By 
Daniel Oh user avatar
Daniel Oh
DZone Core CORE ·
Aug. 07, 26 · Tutorial
Likes (0)
Comment
Save
Tweet
Share
203 Views

Join the DZone community and get the full member experience.

Join For Free

Building autonomous AI agents with large language models (LLMs) is easy when writing single-turn demo scripts. However, moving multi-agent loops into production introduces serious architectural challenges. Agents hallucinate, loop infinitely without reaching convergence, require human approval for high-risk operations, and need standard tool-calling integrations alongside clear operational governance.

Historically, Java developers faced a tough choice: either rely on heavyweight, external workflow clusters (like Temporal or Camunda) that add operational overhead, or hand-craft fragile while loops and custom state machines inside their services.

Quarkus Flow bridges this gap. Built on the Cloud Native Computing Foundation (CNCF) Serverless Workflow specification, Quarkus Flow brings light-footprint, specification-compliant workflow orchestration directly into your Quarkus application. When combined with LangChain4j, Model Context Protocol (MCP) tool connections, and AGENTS.md context governance, Java developers can construct deterministic, observable, and resilient agentic AI workflows using idiomatic CDI and a fluent Java DSL.

The Modern Agentic Stack: Quarkus Flow, MCP, and AGENTS.md

To run production AI agents, you need three distinct layers: orchestration, standardized tool connectivity, and behavioral governance.

  1. Orchestration (Quarkus Flow): Manages state transitions, retries, conditional loops, max-iteration caps, and Human-in-the-Loop (HITL) gates inside the JVM.
  2. Tool standardization (MCP): Connects agents to enterprise data, databases, and APIs using the Model Context Protocol (MCP) without writing custom API adapters for every LLM host.
  3. Behavioral governance (AGENTS.md): A project-level markdown specification that defines system boundaries, agent roles, required output formats, and safety rules that agents read at runtime.
Markdown
 
┌────────────────────────────────────────────────────────────────────────┐
│                        `AGENTS.md` Governance                          │
│        (Runtime System Prompts, Rules & Security Boundaries)           │
└───────────────────────────────────┬────────────────────────────────────┘
                                    │ Loaded via GovernanceLoader
                                    ▼
┌────────────────────────────────────────────────────────────────────────┐
│                 ArticlePublisherWorkflow (Quarkus Flow)                │
│                                                                        │
│  1. generateDraft ──> 2. evaluateDraft ──> 3. reviewCheck              │
│       (Writer)            (Critic)             │                       │
│          ▲                                     │ [approved || >=3]     │
│          │                                     ├───> 5. publishArticle │
│          │         4. reviseDraft <────────────┤                       │
│          └─────────────────┘  [needs revision] │                       │
└──────────────────────────┬─────────────────────────────────────────────┘
                           │
                           │ Tool Invocation via McpToolProvider
                           ▼
┌────────────────────────────────────────────────────────────────────────┐
│                         Stateless MCP Servers                          │
│               (External Data, Database Tools, & APIs)                  │
└────────────────────────────────────────────────────────────────────────┘


Defining Governance With AGENTS.md

Instead of hardcoding prompt strings deep inside Java classes, place an AGENTS.md file in your src/main/resources. This allows developers and prompt engineers to adjust system instructions and security boundaries without re-compiling the application.

Here is the src/main/resources/AGENTS.md file based on the reference repository:

GitHub Flavored Markdown
 
# Content Reviewer Agent Governance & Rules

## Writer Agent Rules
- You are an expert Java and Quarkus developer.
- Draft concise, technically accurate blog posts based on requested topics.
- Query available MCP tools when database context or tool parameters are required.

## Critic Agent Rules
- You are a strict editor reviewing for clarity, security, and technical accuracy.
- Return ONLY a valid JSON object matching this schema:
  {"approved": boolean, "feedback": "string"}

## Security Boundaries
- Do not output shell commands or execute arbitrary code.
- Always enforce character limits and avoid hallucinated imports.


Practical Example: Multi-Agent Workflow With MCP and AGENTS.md

Let's build a production-grade Content Publisher Agent Workflow matching the exact structure from quarkus-flow-mcp-agents. The workflow reads system instructions from AGENTS.md, uses a Writer Agent that fetches real data via an MCP Server, submits the draft to a Critic Agent, and loops until approved or max iterations are reached.

Note: You can find the complete reference implementation repository at https://github.com/danieloh30/quarkus-flow-mcp-agents.git.

1. pom.xml Dependencies

XML
 
...   
  	<properties>
        <compiler-plugin.version>3.15.0</compiler-plugin.version>
        <maven.compiler.release>25</maven.compiler.release>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
        <quarkus.platform.group-id>io.quarkus.platform</quarkus.platform.group-id>
        <quarkus.platform.version>3.38.0</quarkus.platform.version>
        <skipITs>true</skipITs>
        <surefire-plugin.version>3.5.6</surefire-plugin.version>
    </properties>
	
  	<dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>${quarkus.platform.group-id}</groupId>
                <artifactId>${quarkus.platform.artifact-id}</artifactId>
                <version>${quarkus.platform.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>${quarkus.platform.group-id}</groupId>
                <artifactId>quarkus-langchain4j-bom</artifactId>
                <version>${quarkus.platform.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>${quarkus.platform.group-id}</groupId>
                <artifactId>quarkus-flow-bom</artifactId>
                <version>${quarkus.platform.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
		...
        <dependency>
            <groupId>io.quarkiverse.langchain4j</groupId>
            <artifactId>quarkus-langchain4j-openai</artifactId>
        </dependency>
        <dependency>
            <groupId>io.quarkiverse.langchain4j</groupId>
            <artifactId>quarkus-langchain4j-mcp</artifactId>
        </dependency>
        <dependency>
            <groupId>io.quarkiverse.flow</groupId>
            <artifactId>quarkus-flow-langchain4j</artifactId>
        </dependency>
        ...
    </dependencies>
  ...


2. Application Configuration: src/main/resources/application.properties

Properties files
 
# Enable OpenAI
quarkus.langchain4j.openai.api-key=${OPENAI_API_KEY}
quarkus.langchain4j.openai.chat-model.model-name=gpt-4o-mini
quarkus.langchain4j.openai.log-requests=true
quarkus.langchain4j.openai.log-responses=true


3. Orchestrating the Write-Review Loop With @LoopAgent

ArticlePublisher is the orchestrator that wires the multi-agent loop together using Quarkus Flow's declarative API. Here's what each annotation does:

  • @LoopAgent – runs WriterAgent then CriticAgent repeatedly (up to 3 iterations). At build time, Quarkus Flow compiles this into a CNCF Serverless Workflow definition — no separate workflow engine at runtime.
  • @ExitCondition – a static method (isApproved) that checks if the critic's review starts with "APPROVED". It runs after each loop iteration (testExitAtLoopEnd = true). If true, the loop breaks early.
  • @Output – a static method (extractArticle) that extracts the final result. It pulls the draft from the shared agent scope and returns it as the workflow output.
  • The flow: Writer drafts → Critic reviews → if not approved, Writer revises using feedback → repeat until approved or 3 iterations hit → return the final draft.
Java
 
public interface ArticlePublisher {

    @LoopAgent(
            subAgents = { WriterAgent.class, CriticAgent.class },
            maxIterations = 3)
    String publishArticle(String topic);

    @ExitCondition(testExitAtLoopEnd = true,
            description = "Exit when the critic approves the draft")
    static boolean isApproved(String review) {
        return review != null && review.toUpperCase().startsWith("APPROVED");
    }

    @Output
    static String extractArticle(String draft) {
        return draft;
    }
}


4. WriterAgent — Drafting With MCP-Powered Research

WriterAgent is a declarative LLM agent that researches a topic via Brave Search and drafts a technical blog post.

  • @Agent – marks the method as an agent entry point. outputKey = "draft" stores the result in the shared scope so other agents (like CriticAgent) can access it.
  • @ToolBox(WebSearchTool.class) – gives the LLM access to the webSearch tool. The LLM decides when to call it based on the prompt — it's not forced. This is how MCP tools connect to declarative agents.
  • @SystemMessage – instructs the LLM to research before writing, produce accurate content, and revise based on prior feedback. That last part is critical for the loop — on iteration 2+, the LLM sees the critic's feedback in the chat memory and adjusts the draft accordingly.

The interface has no implementation — Quarkus generates it at build time.

Java
 
public interface WriterAgent {

    @Agent(outputKey = "draft",
            description = "Drafts or revises a technical article based on the topic")
    @ToolBox(WebSearchTool.class)
    @SystemMessage("""
            You are an expert Java and Quarkus developer.
            Use the webSearch tool to research the topic before writing.
            Write concise, technically accurate blog drafts based on your research.
            Never generate raw shell commands or suggest unsafe practices.
            If the reviewer has given you feedback in a previous turn, revise the draft to address it.
            """)
    @UserMessage("Write a short technical blog post about: {topic}")
    String writeDraft(String topic);
}


5. CriticAgent — Reviewing for Accuracy and Clarity

CriticAgent is the quality gate in the loop. It reviews the draft and either approves or rejects it with feedback.

  • @Agent – outputKey = "review" stores the review in the shared scope. The @ExitCondition in ArticlePublisher reads this key to decide whether to exit the loop.
  • @UserMessage – injects the {draft} variable from the shared scope, so the critic always reviews the latest version of the article.
  • @SystemMessage – enforces a strict contract: if the draft is acceptable, the response must start with "APPROVED:". This is what makes the @ExitCondition work — it's a simple string check, not another LLM call.

No tools are attached — the critic relies solely on the LLM's reasoning to evaluate the draft.

Java
 
public interface CriticAgent {

    @Agent(outputKey = "review",
            description = "Reviews the draft for technical accuracy and clarity")
    @SystemMessage("""
            You are a strict editor checking for technical accuracy and clarity.
            If the draft is acceptable, your response MUST start with "APPROVED:" followed by a brief note.
            If the draft needs improvement, provide constructive feedback.
            """)
    @UserMessage("""
            Review this draft:

            {draft}
            """)
    String reviewDraft(String draft);
}


5. WebSearchTool — Bridging MCP and Declarative Agents

WebSearchTool is a CDI bean that connects the Brave Search MCP server to the agent workflow.

  • Why it exists – @McpToolBox only works with @RegisterAiService, not with @Agent. This class bridges that gap by creating an MCP client programmatically and exposing it as a @Tool.
  • MCP client setup – the constructor creates a DefaultMcpClient with stdio transport, spawning npx -y @brave/brave-search-mcp-server as a subprocess. The BRAVE_API_KEY is passed via environment variables.
  • @Tool – the webSearch method builds a ToolExecutionRequest targeting the brave_web_search tool on the MCP server, executes it, and returns the results. The LLM sees this as a regular function it can call.
  • @PreDestroy – cleans up the MCP client (and the subprocess) when the CDI context shuts down.

This pattern — wrapping an MCP client in a @Tool CDI bean and attaching it via @ToolBox — is reusable for any MCP server you want to connect to a declarative @Agent.

Java
 
public class WebSearchTool {

    private final McpClient mcpClient;

    WebSearchTool(@ConfigProperty(name = "brave.api.key", defaultValue = "${BRAVE_API_KEY:}") String braveApiKey) {
        mcpClient = new DefaultMcpClient.Builder()
                .transport(new StdioMcpTransport.Builder()
                        .command(List.of("npx", "-y", "@brave/brave-search-mcp-server"))
                        .environment(Map.of("BRAVE_API_KEY", braveApiKey))
                        .logEvents(true)
                        .build())
                .build();
    }

    @Tool("Search the web for up-to-date information about a given query using Brave Search")
    public String webSearch(String query) {
        var request = ToolExecutionRequest.builder()
                .name("brave_web_search")
                .arguments("{\"query\": \"" + query + "\"}")
                .build();
        return mcpClient.executeTool(request).resultText();
    }

    @PreDestroy
    void close() {
        try {
            mcpClient.close();
        } catch (Exception ignored) {
        }
    }
}


Production Guardrails and Enterprise Readiness

Deploying agentic AI systems into enterprise cloud environments requires strict governance, tracing, and high performance:

  • Standardized tools via MCP: By consuming external systems through stateless Model Context Protocol endpoints, tool definitions are decoupled from LLM host code.
  • Context control with AGENTS.md: Business analysts and security leads can audit or update prompt guidelines without re-deploying code artifacts.
  • Human-in-the-loop (HITL): Use Quarkus Flow event filters or pause states to suspend execution until a human administrator approves sensitive tool actions.
  • OpenTelemetry and distributed tracing: Quarkus Flow and quarkus-opentelemetry pass W3C trace contexts across every workflow transition, LLM call, and MCP request.
  • GraalVM native images: Compile the entire stack — Quarkus Flow engine, LangChain4j, MCP connections, and REST interface — into an ultra-fast, native binary with sub-10ms startup times and minimal memory footprint.

By combining Quarkus Flow, LangChain4j, MCP, and AGENTS.md, Java developers can replace unmaintainable AI scripts with clean, specification-compliant, and enterprise-ready agentic architectures.

AI Quarkus Java (programming language)

Opinions expressed by DZone contributors are their own.

Related

  • The Future of Java and AI: Coding in 2025
  • GenAI in Java With Merlinite, Quarkus, and Podman Desktop AI Lab
  • How to Build Living AI Coding Assistants With Quarkus Agent MCP
  • AGENTS.md Makes Your Java Codebase AI-Agent Ready

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

  • RSS
  • X
  • Facebook

ABOUT US

  • About DZone
  • Support and feedback
  • Community research

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 215
  • Nashville, TN 37211
  • [email protected]

Let's be friends:

  • RSS
  • X
  • Facebook