Beyond RAG: Building an AI Coding Agent with Planning, Tool Execution, and ReAct Reasoning A developer built an AI coding agent that uses planning, tool execution, and ReAct reasoning to analyze codebases step-by-step, moving beyond simple RAG-based retrieval. The agent, which works directly against a repository, selects tools like search_code, read_file, and analyze_file to investigate questions such as 'Where is authentication implemented?' The developer addressed challenges in tool selection reliability, avoiding repeated actions, and distinguishing implementation from references. In my previous article, I explored how I wrapped a RAG agent inside an MCP server to make enterprise knowledge accessible through standardized tools. However, while RAG improves retrieval, software engineering tasks require something more. A developer assistant should not only retrieve information. It should investigate. For example: "Where is authentication implemented?" A useful coding assistant should be able to: This led me to build an AI coding agent that can reason, select tools, and analyze a codebase step-by-step. This one doesn't use RAG yet — it works directly against the repository — but that's a deliberate next step, more on that at the end. A traditional chatbot follows a simple pattern: User Question | v LLM | v Response This works well for general questions, but software repositories contain thousands of files and relationships. A coding assistant needs additional capabilities: An agent introduces a decision-making layer: User Question | v Planner | v Choose Tool | v Execute Tool | v Observe Result | v Generate Answer The agent consists of several components. The planner decides the next action. It tries a rule-based plan first keyword matching on the question , and falls back to an LLM via Ollama's tinyllama for JSON-structured tool selection when no rule matches. Example: User question: Where is authentication implemented? Planner response: { "tool": "search code", "input": "authentication" } The planner does not execute the action. It only decides what should happen next. The agent exposes capabilities through tools. Currently: search code read file analyze file Each tool has a specific responsibility. Search Tool — finds files containing a keyword. Input: authentication Output: auth.py , app.py Read File Tool — retrieves source code. Input: auth.py Output: python class AuthenticationService: def login self, username, password : if self.authenticate username, password : return "Login successful" Analyze Tool — understands code structure using Python's ast module. Output: { "classes": "AuthenticationService" , "functions": "login", "authenticate" } The core of the system is the agent loop, following a ReAct-style pattern: Reason ↓ Action ↓ Observation ↓ Reason Again Step 1 — The agent determines it needs to locate authentication code. Action: search code "authentication" Observation: auth.py , app.py Step 2 — The agent identifies that auth.py is likely the implementation. Action: read file "auth.py" Observation: AuthenticationService class found Step 3 — The agent needs more understanding. Action: analyze file Observation: Class: AuthenticationService Functions: login , authenticate The agent now has enough information to answer. Building the agent introduced several interesting engineering challenges. Challenge 1: Reliable tool selection. Initially, the LLM sometimes selected incorrect tools or returned invalid responses. To improve reliability, I restricted tool choices, added JSON validation, and enforced structured outputs. Challenge 2: Avoiding repeated actions. An early version of the agent could repeat search code indefinitely, because every decision was independent. The fix was maintaining previous observations as context, so the planner can see "auth.py contains authentication" and move to reading the file instead of searching again. Challenge 3: Separating implementation from references. A search result may return both auth.py which defines AuthenticationService and app.py which just imports it . The agent needs code analysis, not simple keyword matching, to tell the two apart. For this specific demo question, the agent produces: { "answer": "Authentication is implemented in auth.py", "classes": "AuthenticationService" , "functions": "login", "authenticate" } I want to be upfront about where this stands today: the search, read, and analyze steps are genuinely general-purpose — they work against any Python codebase. The final answer-generation step, however, is currently scoped to authentication-style questions specifically; it doesn't yet generalize its explanation to arbitrary questions the way the reasoning steps before it do. Making that synthesis step question-agnostic is next on my list. Some areas I want to explore next: Building an AI coding agent showed me that the biggest difference between a chatbot and an agent is not the language model itself. The difference is the ability to: RAG helps an AI find information. Agents help an AI perform tasks. The next generation of developer assistants will combine both — and connecting this agent to real enterprise knowledge retrieval is exactly where I'm headed next.