# How to Build a Production-Grade Coding Agent with Tool Calling Capabilities

> Build a production-grade coding agent using LLMs, state management, a tool registry, and ReAct loop for Python function execution. Master structured tool calls.

- Repository: [Bojie Li/ai-agent-book](https://github.com/bojieli/ai-agent-book)
- Tags: how-to-guide
- Published: 2026-07-20

---

**A production-grade coding agent combines an LLM provider, persistent state management, a tool registry with JSON schemas, and a ReAct loop to execute Python functions through structured tool calls.**

Building a production-grade coding agent with tool calling capabilities requires more than connecting an LLM to an API. The `bojieli/ai-agent-book` repository demonstrates a complete, decoupled architecture based on the formula **Agent = LLM + Context + Tools**, enabling the agent to perform file manipulation, shell execution, and web searches through 17 pure-Python tools orchestrated via function calling.

## Architecture Overview: Six Decoupled Layers

The coding agent in this repository follows a layered design where each component can be swapped or extended independently. Understanding these layers is essential before implementing your own agent.

| Layer | Responsibility | Key Implementation |
|------|----------------|---------------------|
| **LLM Provider** | Connects to Anthropic, OpenAI, or OpenRouter APIs with streaming | [`PROVIDERS.md`](https://github.com/bojieli/ai-agent-book/blob/main/PROVIDERS.md) |
| **System Prompt & State** | Supplies static instructions, timestamps, and persistent JSON state | [`system-prompt.md`](https://github.com/bojieli/ai-agent-book/blob/main/system-prompt.md) & [`system_state.py`](https://github.com/bojieli/ai-agent-book/blob/main/system_state.py) |
| **Tool Registry** | Registers tools, maps LLM schemas to Python callables, enforces safety | [`tool_registry.py`](https://github.com/bojieli/ai-agent-book/blob/main/tool_registry.py) |
| **Tool Implementations** | Pure-Python utilities without external CLI dependencies | `tools/*.py` |
| **Agent Core** | Orchestrates the ReAct loop, parses tool calls, updates context | [`agent.py`](https://github.com/bojieli/ai-agent-book/blob/main/agent.py) & [`agent_new.py`](https://github.com/bojieli/ai-agent-book/blob/main/agent_new.py) |
| **Entry Point** | Loads configuration and executes user tasks | [`main.py`](https://github.com/bojieli/ai-agent-book/blob/main/main.py) & [`quickstart.py`](https://github.com/bojieli/ai-agent-book/blob/main/quickstart.py) |

## LLM Provider Abstraction

The agent abstracts the underlying model behind a provider interface documented in [`PROVIDERS.md`](https://github.com/bojieli/ai-agent-book/blob/main/PROVIDERS.md). By default, the system uses the Anthropic `claude-3-5-sonnet-20240620` model, but you can switch providers by modifying [`config.py`](https://github.com/bojieli/ai-agent-book/blob/main/config.py) or setting environment variables like `ANTHROPIC_API_KEY` or `OPENAI_API_KEY`.

The provider streams token-by-token output, which the agent consumes to build a **thought-process log** stored in the conversation context. This streaming approach enables real-time monitoring of the agent's reasoning before it commits to a tool call.

## System Prompt and Persistent State

The **system prompt** resides in [`system-prompt.md`](https://github.com/bojieli/ai-agent-book/blob/main/system-prompt.md) and encodes the agent's persona, timestamp tracking, and TODO list management. It explicitly instructs the LLM on how to request tool calls using JSON schemas.

State persistence is handled by [`system_state.py`](https://github.com/bojieli/ai-agent-book/blob/main/system_state.py), which saves the agent's internal state—including the current task, completed steps, and tool-call counters—to a JSON file. This design ensures the agent can resume after a crash without losing context.

## Tool Registry and Schema Mapping

All callable tools are registered in [`tool_registry.py`](https://github.com/bojieli/ai-agent-book/blob/main/tool_registry.py). This module builds a JSON schema that the LLM can understand (containing function names, descriptions, and parameters) and maps each schema entry to a concrete Python callable.

When the LLM returns a `function_call` block, the agent performs the following steps:

1. Parses the JSON arguments
2. Looks up the corresponding callable in the registry
3. Validates arguments against the schema
4. Executes the tool safely
5. Injects the tool's result back into the conversation as a system message

## Pure-Python Tool Implementations

The repository ships **17 fully implemented tools** covering common development tasks. All implementations are pure Python to ensure cross-platform compatibility without requiring external binaries like `ripgrep`.

| Tool | Purpose | File |
|------|---------|------|
| `write_tool` | Create or overwrite files | [`tools/write_tool.py`](https://github.com/bojieli/ai-agent-book/blob/main/tools/write_tool.py) |
| `read_tool` | Read file contents | [`tools/read_tool.py`](https://github.com/bojieli/ai-agent-book/blob/main/tools/read_tool.py) |
| `ls_tool` | List directory contents | [`tools/ls_tool.py`](https://github.com/bojieli/ai-agent-book/blob/main/tools/ls_tool.py) |
| `grep_tool` | Pure-Python search matching ripgrep functionality | [`tools/grep_tool.py`](https://github.com/bojieli/ai-agent-book/blob/main/tools/grep_tool.py) |
| `glob_tool` | Filename pattern matching | [`tools/glob_tool.py`](https://github.com/bojieli/ai-agent-book/blob/main/tools/glob_tool.py) |
| `bash_tool` | Execute shell commands (sandboxed) | [`tools/bash_tool.py`](https://github.com/bojieli/ai-agent-book/blob/main/tools/bash_tool.py) |
| `bash_output_tool` | Capture stdout/stderr | [`tools/bash_output_tool.py`](https://github.com/bojieli/ai-agent-book/blob/main/tools/bash_output_tool.py) |
| `web_search_tool` | DuckDuckGo web search | [`tools/web_search_tool.py`](https://github.com/bojieli/ai-agent-book/blob/main/tools/web_search_tool.py) |
| `web_fetch_tool` | Retrieve raw URLs | [`tools/web_fetch_tool.py`](https://github.com/bojieli/ai-agent-book/blob/main/tools/web_fetch_tool.py) |
| `todo_write_tool` | Append to TODO files | [`tools/todo_write_tool.py`](https://github.com/bojieli/ai-agent-book/blob/main/tools/todo_write_tool.py) |
| `edit_tool` | In-place regex editing | [`tools/edit_tool.py`](https://github.com/bojieli/ai-agent-book/blob/main/tools/edit_tool.py) |
| `multi_edit_tool` | Batch edits across files | [`tools/multi_edit_tool.py`](https://github.com/bojieli/ai-agent-book/blob/main/tools/multi_edit_tool.py) |
| `notebook_edit_tool` | Manipulate Jupyter notebooks | [`tools/notebook_edit_tool.py`](https://github.com/bojieli/ai-agent-book/blob/main/tools/notebook_edit_tool.py) |
| `shell_session` | Persistent interactive bash | [`tools/shell_session.py`](https://github.com/bojieli/ai-agent-book/blob/main/tools/shell_session.py) |
| `kill_bash_tool` | Terminate bash sessions | [`tools/kill_bash_tool.py`](https://github.com/bojieli/ai-agent-book/blob/main/tools/kill_bash_tool.py) |
| `exit_plan_mode_tool` | Switch to plan-only mode | [`tools/exit_plan_mode_tool.py`](https://github.com/bojieli/ai-agent-book/blob/main/tools/exit_plan_mode_tool.py) |
| `task_tool` | High-level task orchestration | [`tools/task_tool.py`](https://github.com/bojieli/ai-agent-book/blob/main/tools/task_tool.py) |

## Agent Core: The ReAct Loop

The reasoning and acting loop is implemented in [`agent.py`](https://github.com/bojieli/ai-agent-book/blob/main/agent.py) (original) and [`agent_new.py`](https://github.com/bojieli/ai-agent-book/blob/main/agent_new.py) (refactored with improved state handling). The workflow follows this deterministic sequence:

1. **Compose prompt** = system prompt + conversation history + tool schemas
2. **Call LLM** → receive response potentially containing `function_call`
3. **If function call present**: Parse JSON, dispatch via registry, execute tool, append result to context
4. **If no function call**: Treat response as final output and return to user
5. **Iterate** until task completion or max-turn limit reached

This loop enables multi-step reasoning workflows such as "search codebase → read relevant files → edit implementation → run tests" without losing state between turns.

## Entry Point and Configuration

The [`main.py`](https://github.com/bojieli/ai-agent-book/blob/main/main.py) file demonstrates how to wire components together:

```python
from coding_agent.agent_new import CodingAgent
from coding_agent.config import Config

# Load configuration from environment variables

cfg = Config.load()
agent = CodingAgent(cfg)

# Execute a coding task

result = agent.run("Implement a Fibonacci function in Python")
print(result)

```

The [`quickstart.py`](https://github.com/bojieli/ai-agent-book/blob/main/quickstart.py) script provides a ready-to-run demo showcasing end-to-end task completion.

## Practical Implementation Examples

### Instantiating the Agent

```python
from coding_agent.agent_new import CodingAgent
from coding_agent.config import Config

# Load configuration (expects ANTHROPIC_API_KEY in .env)

cfg = Config.load()
agent = CodingAgent(cfg)

```

### Running a Coding Task

```python
task = """
Write a Python function `fib(n)` that returns the nth Fibonacci number.
Add a unit test for n=10.
"""
output = agent.run(task)

```

The agent will automatically:
- Generate the function code
- Call `write_tool` to save the file
- Call `bash_tool` to run `pytest`
- Return the test results

### Direct Tool Invocation

For manual tool usage bypassing the LLM:

```python
from coding_agent.tools.grep_tool import GrepTool

grep = GrepTool()
matches = grep.run({
    "path": "chapter5/coding-agent",
    "pattern": "def run"
})

```

### Extending with Custom Tools

Create [`tools/markdown_formatter.py`](https://github.com/bojieli/ai-agent-book/blob/main/tools/markdown_formatter.py):

```python
from typing import TypedDict
from coding_agent.base import BaseTool

class Args(TypedDict):
    text: str

class MarkdownFormatter(BaseTool):
    name = "markdown_formatter"
    description = "Format raw text into valid Markdown."
    args_schema = Args

    def run(self, args: Args) -> str:
        return f"# {args['text'].strip()}"

```

Register in [`tool_registry.py`](https://github.com/bojieli/ai-agent-book/blob/main/tool_registry.py):

```python
from .tools.markdown_formatter import MarkdownFormatter
registry.register(MarkdownFormatter())

```

## Summary

- **Decoupled architecture**: The agent separates LLM providers, state management, tool registries, and implementations into six independent layers
- **Pure-Python tools**: All 17 tools avoid external CLI dependencies, ensuring portability across macOS, Linux, and Windows
- **ReAct loop**: The core [`agent.py`](https://github.com/bojieli/ai-agent-book/blob/main/agent.py) implements a deterministic loop that parses tool calls, executes them safely, and maintains conversation context
- **Extensibility**: New tools inherit from `BaseTool`, define a TypedDict schema, and register via [`tool_registry.py`](https://github.com/bojieli/ai-agent-book/blob/main/tool_registry.py) to become available to the LLM
- **Production resilience**: [`system_state.py`](https://github.com/bojieli/ai-agent-book/blob/main/system_state.py) persists state to JSON, enabling crash recovery and long-running task management

## Frequently Asked Questions

### What is the ReAct loop in coding agents?

The **ReAct loop** (Reasoning and Acting) is the core orchestration pattern in [`agent.py`](https://github.com/bojieli/ai-agent-book/blob/main/agent.py) that alternates between LLM reasoning and tool execution. The agent receives a prompt, the LLM decides whether to make a tool call or provide a final answer, and the loop executes the tool and feeds results back into the context. This enables multi-step problem solving while maintaining a coherent conversation history.

### How does the tool registry ensure type safety?

The [`tool_registry.py`](https://github.com/bojieli/ai-agent-book/blob/main/tool_registry.py) module enforces type safety by mapping each tool's JSON schema to a Python **TypedDict**. When the LLM returns a function call, the registry validates that the provided arguments match the expected types before invoking the tool's `run` method, preventing runtime errors from malformed LLM outputs.

### Can I use models other than Claude?

Yes. The provider system documented in [`PROVIDERS.md`](https://github.com/bojieli/ai-agent-book/blob/main/PROVIDERS.md) supports Anthropic, OpenAI, and OpenRouter APIs. You configure your preferred provider by setting the appropriate environment variables (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, etc.) and adjusting the model name in [`config.py`](https://github.com/bojieli/ai-agent-book/blob/main/config.py) without modifying the agent core logic.

### How does the agent maintain context across long-running tasks?

The [`system_state.py`](https://github.com/bojieli/ai-agent-book/blob/main/system_state.py) module persists the conversation history, current task status, and TODO lists to a JSON file on disk. If the process crashes or restarts, the agent reloads this state and resumes from the last completed step, ensuring that long coding tasks don't lose progress due to interruptions.