{"slug": "building-an-ai-agent-system-with-the-react-pattern-in-java", "title": "Building an AI Agent System with the ReACT Pattern in Java", "summary": "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.", "body_md": "From answering questions to solving problems — Phase 6 of the Jarvis AI Platform\n\nAfter 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.\n\n```\nYou: \"What's the weather in Kathmandu?\"\n\nWhisper\n    ↓\nAiOrchestrator\n    ↓\nWeatherTool\n    ↓\nText-to-Speech\n\nJarvis:\n\"It is 22°C and clear.\"\n```\n\nThat works well for simple questions.\n\nIt completely breaks down when a task requires multiple decisions.\n\nImagine asking:\n\n```\nResearch the top 3 Java AI frameworks,\ncompare them,\nand summarize the findings.\n```\n\nA traditional chatbot usually replies:\n\nI don't have enough information to research that.\n\nThe problem isn't intelligence.\n\nThe problem is planning.\n\nTo answer properly, the AI must:\n\nThat requires multiple tool calls and reasoning between each one.\n\nThis is exactly what AI agents are designed to do.\n\nReACT stands for:\n\n**Reason + Act**\n\nInstead of generating one response, the AI repeatedly performs a reasoning loop.\n\n```\nTHINK\n↓\nACT\n↓\nOBSERVE\n↓\nTHINK\n↓\nACT\n↓\nOBSERVE\n↓\nFINAL ANSWER\n```\n\nExample:\n\n```\nTHOUGHT:\nI should search for Java AI frameworks.\n\nACTION:\nsearch\n\nINPUT:\nJava AI frameworks 2026\n\n↓\n\nOBSERVATION:\nSpring AI\nLangChain4j\nSemantic Kernel\n\n↓\n\nTHOUGHT:\nNow I need comparison data.\n\n↓\n\nACTION:\nsearch\n\nINPUT:\nSpring AI vs LangChain4j\n\n↓\n\nFINAL ANSWER\n```\n\nInstead of guessing everything up front, the AI gathers information step by step before producing the final response.\n\nThe most important design decision of Phase 6 was **not modifying the existing chat pipeline**.\n\nInstead of turning `AiOrchestrator`\n\ninto a giant class responsible for both chat and agents, agents became a completely separate orchestration layer.\n\n```\n❌ Wrong\n\nAiOrchestrator\n    ↓\nSingle Chat\n    ↓\nAgent Logic\n    ↓\nTool Logic\n    ↓\nEverything Mixed Together\n\n✅ Correct\n\nAgentController\n        ↓\nAgentOrchestrator\n        ↓\nAgentExecutor\n        ↓\nAgentPlanner\n        ↓\nToolRegistry\n\nAiOrchestrator\n        ↑\nRemains Completely Unchanged\n```\n\nEverything built during Phases 1–5 continues working exactly as before.\n\nAgents simply reuse the existing tools.\n\nThe final architecture looks like this.\n\n```\nAgentController\n        ↓\nAgentOrchestrator\n        ↓\nAgentExecutor\n        ↓\nAgentPlanner\n        ↓\nToolRegistry\n```\n\nEach component has a single responsibility.\n\nKeeping these responsibilities isolated made the implementation significantly easier to maintain.\n\nThe planner doesn't simply ask the AI for an answer.\n\nInstead, it asks for structured output.\n\n```\nYou are an AI agent.\n\nAvailable tools:\n\n- getWeather\n- calculate\n- search\n\nFor every step respond exactly as:\n\nTHOUGHT:\n...\n\nACTION:\n...\n\nINPUT:\n...\n```\n\nWhen enough information has been gathered:\n\n```\nTHOUGHT:\n...\n\nFINAL_ANSWER:\n...\n```\n\nThis prompt acts as a contract between the model and the parser.\n\nThe first implementation used `indexOf()`\n\n.\n\n```\nresponse.indexOf(\"ACTION:\");\n```\n\nThat failed whenever the literal text `ACTION:`\n\nappeared inside user data.\n\nThe solution was precompiled regular expressions anchored to the beginning of each line.\n\n```\nprivate static final Pattern ACTION_PATTERN =\n    Pattern.compile(\n        \"(?ms)^ACTION:\\\\s*(.*?)\"\n            + \"(?=^(?:THOUGHT:|INPUT:|FINAL_ANSWER:)|\\\\z)\");\n```\n\nThis guarantees that only real section headers are parsed.\n\nThe executor coordinates the complete lifecycle.\n\n```\npublic Flux<AgentEvent> execute(\n        Agent agent,\n        UUID userId) {\n\n    return Flux.create(sink ->\n            runLoop(sink, agent, userId))\n        .subscribeOn(Schedulers.boundedElastic())\n        .timeout(TOTAL_TIMEOUT);\n}\n```\n\nA few design decisions are worth highlighting.\n\n`Flux.generate()`\n\nallows only one event per iteration.\n\nAgents frequently emit multiple events:\n\n`Flux.create()`\n\nsupports that naturally.\n\nPlanning calls, database writes, and tool execution are blocking operations.\n\nMoving the entire loop onto `boundedElastic()`\n\nkeeps the WebFlux event loop free.\n\nEvery agent is protected by:\n\nAgents can never run forever.\n\nInitially each event incremented the step counter independently.\n\n```\nTHINK → Step 0\n\nACT → Step 1\n\nOBSERVE → Step 2\n```\n\nThose three events actually belong to the same logical step.\n\nThe fix was simple.\n\nCapture the current step once.\n\n```\nfinal int currentStep = stepIndex;\n\nemitThink(currentStep);\n\nemitAct(currentStep);\n\nemitObserve(currentStep);\n\nstepIndex++;\n```\n\nNow every event generated during one reasoning cycle shares the same step number.\n\nOriginally tool dispatch used substring matching.\n\n```\nmethod.contains(toolName)\n```\n\nThis produced unexpected matches.\n\n```\nsearch\n↓\n\nwebSearch\n\n↓\n\nsearchDocuments\n```\n\nThe correct implementation performs exact matching.\n\n```\nmethod.equalsIgnoreCase(toolName.trim())\n```\n\nBecause the system prompt already specifies the exact method names, exact matching is both safer and simpler.\n\nAgents execute for much longer than a normal chat response.\n\nThe browser shouldn't wait until everything finishes.\n\nInstead, every reasoning step is streamed immediately.\n\n```\nevent: think\n\nevent: act\n\nevent: observe\n\nevent: final\n\nevent: done\n```\n\nUsers can literally watch the AI think.\n\nOne subtle problem appeared during testing.\n\nIf a browser tab closed, the agent continued executing in the background.\n\nThe fix required checking cancellation inside every loop iteration.\n\n```\nif (sink.isCancelled()) {\n    return;\n}\n```\n\nOne small check prevents wasted CPU time and unnecessary background work.\n\nAgents move through a strict lifecycle.\n\n```\nPENDING\n    ↓\nRUNNING\n    ↓\nCOMPLETED\n\nor\n\nFAILED\n\nor\n\nCANCELLED\n```\n\nInvalid transitions are rejected directly by the domain model.\n\n```\nagent.withRunning();\n\nagent.withCompleted();\n\nagent.withFailed();\n```\n\nThe service layer doesn't enforce state rules.\n\nThe domain object does.\n\nConcurrent updates introduced another challenge.\n\nA completion event and an error event could arrive simultaneously.\n\nInstead of overwriting each other, updates use compare-and-set semantics.\n\n```\nUPDATE agents\n\nSET status = :newStatus\n\nWHERE id = :id\n\nAND status = :expectedStatus\n```\n\nIf another thread already changed the state, zero rows are updated.\n\nNo race conditions.\n\nNo silent overwrites.\n\nThe complete agent system exposes six endpoints.\n\n```\nPOST   /api/v1/agents/stream\nPOST   /api/v1/agents\nGET    /api/v1/agents\nGET    /api/v1/agents/{id}\nGET    /api/v1/agents/{id}/steps\nDELETE /api/v1/agents/{id}\n```\n\nThe streaming endpoint returns live ReACT events while the asynchronous endpoint starts long-running agents without holding the HTTP request open.\n\n```\nUser\n\n↓\n\n\"What is the weather in London\nand Tokyo,\nand what time is it there?\"\n\n↓\n\nTHINK\n\n↓\n\nWeather Tool\n\n↓\n\nTime Tool\n\n↓\n\nWeather Tool\n\n↓\n\nTime Tool\n\n↓\n\nFINAL ANSWER\n```\n\nOne request.\n\nMultiple tools.\n\nOne coherent response.\n\nNo Python.\n\nNo LangChain.\n\nPure Java with Spring AI.\n\nThe parser expects a specific format.\n\nAny ambiguity breaks the workflow.\n\nPrompt engineering matters just as much as parser implementation.\n\nAI models occasionally produce malformed output.\n\nRather than failing, Jarvis treats unknown responses as a final answer and continues.\n\nThe `Agent`\n\nobject owns its lifecycle.\n\nImpossible transitions become impossible states.\n\nMultiple asynchronous events may update the same row.\n\nChecking the expected state inside SQL eliminates lost updates.\n\nRunning on an Intel Core Ultra 7 with 16 GB RAM:\n\n| Operation | Typical Time |\n|---|---|\n| Agent creation | ~10 ms |\n| AI planning | 2–8 s |\n| Tool execution | 50–500 ms |\n| Step persistence | ~10 ms |\n| Typical 3-step task | 10–25 s |\n\nThe AI planning phase dominates overall execution time.\n\nPhase 7 introduces the complete web interface.\n\nIt brings together everything built so far:\n\nThe backend is complete.\n\nThe next challenge is building the frontend.\n\nJarvis is open source under the Apache 2.0 License.\n\nCurrent contributor-friendly issues include:\n\n```\n#84  CLI agent commands\n\n#85  Agent REST API integration tests\n\n#66  CLI tool commands\n\n#34  CLI memory commands\n```\n\nGitHub:\n\n```\nhttps://github.com/sujankim/jarvis-ai-platform\n```\n\nYour AI. Your Data. Your Machine.", "url": "https://wpnews.pro/news/building-an-ai-agent-system-with-the-react-pattern-in-java", "canonical_source": "https://dev.to/sujankim/building-an-ai-agent-system-with-the-react-pattern-in-java-18k3", "published_at": "2026-07-09 06:11:27+00:00", "updated_at": "2026-07-09 06:41:27.520673+00:00", "lang": "en", "topics": ["ai-agents", "artificial-intelligence", "large-language-models", "developer-tools"], "entities": ["Jarvis AI Platform", "ReACT", "Java", "Spring AI", "LangChain4j", "Semantic Kernel"], "alternates": {"html": "https://wpnews.pro/news/building-an-ai-agent-system-with-the-react-pattern-in-java", "markdown": "https://wpnews.pro/news/building-an-ai-agent-system-with-the-react-pattern-in-java.md", "text": "https://wpnews.pro/news/building-an-ai-agent-system-with-the-react-pattern-in-java.txt", "jsonld": "https://wpnews.pro/news/building-an-ai-agent-system-with-the-react-pattern-in-java.jsonld"}}