cd /news/ai-agents/building-an-ai-agent-system-with-the… Β· home β€Ί topics β€Ί ai-agents β€Ί article
[ARTICLE Β· art-52166] src=dev.to β†— pub= topic=ai-agents verified=true sentiment=↑ positive

Building an AI Agent System with the ReACT Pattern in Java

A developer building the Jarvis AI Platform implemented Phase 6, adding an AI agent system using the ReACT (Reason + Act) pattern in Java. The system handles multi-step tasks by repeatedly reasoning, acting, and observing, with a separate orchestration layer that reuses existing tools. Key design decisions include using structured prompts for planning and regex-based parsing to reliably extract action steps.

read6 min views1 publishedJul 9, 2026

From answering questions to solving problems β€” Phase 6 of the Jarvis AI Platform

After Phase 5, Jarvis could hear, speak, remember conversations, retrieve documents, and use tools. But every interaction was still limited to a single request and a single response.

You: "What's the weather in Kathmandu?"

Whisper
    ↓
AiOrchestrator
    ↓
WeatherTool
    ↓
Text-to-Speech

Jarvis:
"It is 22Β°C and clear."

That works well for simple questions.

It completely breaks down when a task requires multiple decisions.

Imagine asking:

Research the top 3 Java AI frameworks,
compare them,
and summarize the findings.

A traditional chatbot usually replies:

I don't have enough information to research that.

The problem isn't intelligence.

The problem is planning.

To answer properly, the AI must:

That requires multiple tool calls and reasoning between each one.

This is exactly what AI agents are designed to do.

ReACT stands for:

Reason + Act

Instead of generating one response, the AI repeatedly performs a reasoning loop.

THINK
↓
ACT
↓
OBSERVE
↓
THINK
↓
ACT
↓
OBSERVE
↓
FINAL ANSWER

Example:

THOUGHT:
I should search for Java AI frameworks.

ACTION:
search

INPUT:
Java AI frameworks 2026

↓

OBSERVATION:
Spring AI
LangChain4j
Semantic Kernel

↓

THOUGHT:
Now I need comparison data.

↓

ACTION:
search

INPUT:
Spring AI vs LangChain4j

↓

FINAL ANSWER

Instead of guessing everything up front, the AI gathers information step by step before producing the final response.

The most important design decision of Phase 6 was not modifying the existing chat pipeline.

Instead of turning AiOrchestrator

into a giant class responsible for both chat and agents, agents became a completely separate orchestration layer.

❌ Wrong

AiOrchestrator
    ↓
Single Chat
    ↓
Agent Logic
    ↓
Tool Logic
    ↓
Everything Mixed Together

βœ… Correct

AgentController
        ↓
AgentOrchestrator
        ↓
AgentExecutor
        ↓
AgentPlanner
        ↓
ToolRegistry

AiOrchestrator
        ↑
Remains Completely Unchanged

Everything built during Phases 1–5 continues working exactly as before.

Agents simply reuse the existing tools.

The final architecture looks like this.

AgentController
        ↓
AgentOrchestrator
        ↓
AgentExecutor
        ↓
AgentPlanner
        ↓
ToolRegistry

Each component has a single responsibility.

Keeping these responsibilities isolated made the implementation significantly easier to maintain.

The planner doesn't simply ask the AI for an answer.

Instead, it asks for structured output.

You are an AI agent.

Available tools:

- getWeather
- calculate
- search

For every step respond exactly as:

THOUGHT:
...

ACTION:
...

INPUT:
...

When enough information has been gathered:

THOUGHT:
...

FINAL_ANSWER:
...

This prompt acts as a contract between the model and the parser.

The first implementation used indexOf()

.

response.indexOf("ACTION:");

That failed whenever the literal text ACTION:

appeared inside user data.

The solution was precompiled regular expressions anchored to the beginning of each line.

private static final Pattern ACTION_PATTERN =
    Pattern.compile(
        "(?ms)^ACTION:\\s*(.*?)"
            + "(?=^(?:THOUGHT:|INPUT:|FINAL_ANSWER:)|\\z)");

This guarantees that only real section headers are parsed.

The executor coordinates the complete lifecycle.

public Flux<AgentEvent> execute(
        Agent agent,
        UUID userId) {

    return Flux.create(sink ->
            runLoop(sink, agent, userId))
        .subscribeOn(Schedulers.boundedElastic())
        .timeout(TOTAL_TIMEOUT);
}

A few design decisions are worth highlighting.

Flux.generate()

allows only one event per iteration.

Agents frequently emit multiple events:

Flux.create()

supports that naturally.

Planning calls, database writes, and tool execution are blocking operations.

Moving the entire loop onto boundedElastic()

keeps the WebFlux event loop free.

Every agent is protected by:

Agents can never run forever.

Initially each event incremented the step counter independently.

THINK β†’ Step 0

ACT β†’ Step 1

OBSERVE β†’ Step 2

Those three events actually belong to the same logical step.

The fix was simple.

Capture the current step once.

final int currentStep = stepIndex;

emitThink(currentStep);

emitAct(currentStep);

emitObserve(currentStep);

stepIndex++;

Now every event generated during one reasoning cycle shares the same step number.

Originally tool dispatch used substring matching.

method.contains(toolName)

This produced unexpected matches.

search
↓

webSearch

↓

searchDocuments

The correct implementation performs exact matching.

method.equalsIgnoreCase(toolName.trim())

Because the system prompt already specifies the exact method names, exact matching is both safer and simpler.

Agents execute for much longer than a normal chat response.

The browser shouldn't wait until everything finishes.

Instead, every reasoning step is streamed immediately.

event: think

event: act

event: observe

event: final

event: done

Users can literally watch the AI think.

One subtle problem appeared during testing.

If a browser tab closed, the agent continued executing in the background.

The fix required checking cancellation inside every loop iteration.

if (sink.isCancelled()) {
    return;
}

One small check prevents wasted CPU time and unnecessary background work.

Agents move through a strict lifecycle.

PENDING
    ↓
RUNNING
    ↓
COMPLETED

or

FAILED

or

CANCELLED

Invalid transitions are rejected directly by the domain model.

agent.withRunning();

agent.withCompleted();

agent.withFailed();

The service layer doesn't enforce state rules.

The domain object does.

Concurrent updates introduced another challenge.

A completion event and an error event could arrive simultaneously.

Instead of overwriting each other, updates use compare-and-set semantics.

UPDATE agents

SET status = :newStatus

WHERE id = :id

AND status = :expectedStatus

If another thread already changed the state, zero rows are updated.

No race conditions.

No silent overwrites.

The complete agent system exposes six endpoints.

POST   /api/v1/agents/stream
POST   /api/v1/agents
GET    /api/v1/agents
GET    /api/v1/agents/{id}
GET    /api/v1/agents/{id}/steps
DELETE /api/v1/agents/{id}

The streaming endpoint returns live ReACT events while the asynchronous endpoint starts long-running agents without holding the HTTP request open.

User

↓

"What is the weather in London
and Tokyo,
and what time is it there?"

↓

THINK

↓

Weather Tool

↓

Time Tool

↓

Weather Tool

↓

Time Tool

↓

FINAL ANSWER

One request.

Multiple tools.

One coherent response.

No Python.

No LangChain.

Pure Java with Spring AI.

The parser expects a specific format.

Any ambiguity breaks the workflow.

Prompt engineering matters just as much as parser implementation.

AI models occasionally produce malformed output.

Rather than failing, Jarvis treats unknown responses as a final answer and continues.

The Agent

object owns its lifecycle.

Impossible transitions become impossible states.

Multiple asynchronous events may update the same row.

Checking the expected state inside SQL eliminates lost updates.

Running on an Intel Core Ultra 7 with 16 GB RAM:

Operation Typical Time
Agent creation ~10 ms
AI planning 2–8 s
Tool execution 50–500 ms
Step persistence ~10 ms
Typical 3-step task 10–25 s

The AI planning phase dominates overall execution time.

Phase 7 introduces the complete web interface.

It brings together everything built so far:

The backend is complete.

The next challenge is building the frontend.

Jarvis is open source under the Apache 2.0 License.

Current contributor-friendly issues include:

#84  CLI agent commands

#85  Agent REST API integration tests

#66  CLI tool commands

#34  CLI memory commands

GitHub:

https://github.com/sujankim/jarvis-ai-platform

Your AI. Your Data. Your Machine.

── more in #ai-agents 4 stories Β· sorted by recency
── more on @jarvis ai platform 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain β€” perfect for shipping the agent you just read about.

$git push zahid main
β†’ Live at https://your-agent.zahid.host βœ“
Get free account β†’ Pricing
from €0/mo Β· no card required
LIVE [news/building-an-ai-agent…] indexed:0 read:6min 2026-07-09 Β· β€”