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.
The Limitation of Single-Turn AI
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:
- Search for Java AI frameworks
- Search for comparisons
- Gather information
- Analyze results
- Produce a summary
That requires multiple tool calls and reasoning between each one.
This is exactly what AI agents are designed to do.
What Is the ReACT Pattern?
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 Biggest Architectural Decision
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 Four-Layer Agent System
The final architecture looks like this.
AgentController
↓
AgentOrchestrator
↓
AgentExecutor
↓
AgentPlanner
↓
ToolRegistry
Each component has a single responsibility.
- AgentController exposes the REST API.
- AgentOrchestrator manages the agent lifecycle.
- AgentExecutor runs the ReACT loop.
- AgentPlanner asks the AI what to do next.
- ToolRegistry executes the selected tool.
Keeping these responsibilities isolated made the implementation significantly easier to maintain.
Teaching the AI to Think
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.
Parsing Structured Output Correctly
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 ReACT Execution Loop
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.create()
Flux.generate() allows only one event per iteration.
Agents frequently emit multiple events:
- THINK
- ACT
- OBSERVE
Flux.create() supports that naturally.
boundedElastic()
Planning calls, database writes, and tool execution are blocking operations.
Moving the entire loop onto boundedElastic() keeps the WebFlux event loop free.
Safety Limits
Every agent is protected by:
- Maximum step count
- Step timeout
- Total execution timeout
Agents can never run forever.
Fixing the Step Index Bug
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.
Exact Tool Matching
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.
Streaming Agent Events
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.
Handling Client Disconnects
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.
Agent State Machine
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.
Compare-and-Set Updates
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.
REST API
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.
A Complete Agent Execution
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.
Lessons Learned
Structured prompts are contracts
The parser expects a specific format.
Any ambiguity breaks the workflow.
Prompt engineering matters just as much as parser implementation.
Graceful degradation wins
AI models occasionally produce malformed output.
Rather than failing, Jarvis treats unknown responses as a final answer and continues.
Domain models should enforce rules
The Agent object owns its lifecycle.
Impossible transitions become impossible states.
Compare-and-set prevents races
Multiple asynchronous events may update the same row.
Checking the expected state inside SQL eliminates lost updates.
Performance
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.
What's Next
Phase 7 introduces the complete web interface.
It brings together everything built so far:
- Real-time chat
- Agent dashboard
- Memory management
- Document search
- Voice interface
- Settings management
The backend is complete.
The next challenge is building the frontend.
Contributing
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
Jarvis AI Platform Series
- Part 1 — Building a Local-First AI Assistant with Spring Boot 4
- Part 2 — Building Long-Term Memory with pgvector
- Part 3 — Implementing Semantic Memory Retrieval
- Part 4 — Building a Tool Engine with Spring AI
- Part 5 — Adding Voice with Whisper and Text-to-Speech
- Part 6 — Building an AI Agent System with the ReACT Pattern (this article)
- Part 7 — Building the Web UI (coming next)
Your AI. Your Data. Your Machine.
Top comments (0)