A powerful, provider-agnostic autonomous coding agent using a ReAct (Reasoning, Acting, Observing) loop. Supports multiple LLM providers through a unified interface.
- Multi-Provider Support: Seamlessly switch between Anthropic (Claude), OpenAI, Google Gemini, and Together AI
- Tool Use: Equipped with tools for file manipulation, system commands, Python REPL, and web search
- Streaming: Real-time streaming responses with reasoning display
- Multi-Agent Mode: Optional supervisor + specialist workers (configured in
config.yaml) - Human-in-the-Loop: Interrupt pattern for agent-user clarification via
ask_usertool - Security: Path validation, command sanitization, and Python code inspection
- API Server: Optional FastAPI server (
--serve) - Data Analysis Tools: Dedicated dataset tools (CSV/JSON/JSONL/XLSX, export, plot saving)
- Configuration: Easy configuration via
config.yaml, environment variables, or CLI flags
┌─────────────────────────────────────────────────────────────┐
│ CodingAgent │
│ ┌─────────────┐ ┌──────────────┐ ┌─────────────────┐ │
│ │ History │ │ StreamHandler│ │ Tool Registry │ │
│ └─────────────┘ └──────────────┘ └─────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Client Factory │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │
│ │ Anthropic│ │ OpenAI │ │ Google │ │ Together │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────┘
- Language: Python 3.12+
- Dependency Management:
uv - LLM Clients:
anthropic,openai,google-genai,together - Search:
tavily-python
-
Clone the repository:
git clone https://github.com/OrMullerHahitti/coding-agent/ cd coding-agent -
Install dependencies:
uv sync
Optional extras:
uv sync --extra api # API server (FastAPI/Uvicorn) uv sync --extra data # XLSX + plot saving (openpyxl/matplotlib) uv sync --extra all # everything
-
Set up environment variables:
cp .env.example .env # Edit .env with your API keys
# basic usage (auto-detects provider from available API keys)
uv run python -m coding_agent.main
# with streaming
uv run python -m coding_agent.main --stream
# with verbose output (shows reasoning)
uv run python -m coding_agent.main --verbose
# specify provider and model
uv run python -m coding_agent.main --provider anthropic --model claude-3-5-sonnet-20240620
# with debug logging
uv run python -m coding_agent.main --log-level DEBUG
# start API server
uv run python -m coding_agent.main --serve
# multi-agent mode (uses multi_agent config in config.yaml)
uv run python -m coding_agent.main --multi-agent
# multi-agent with selected workers
uv run python -m coding_agent.main --multi-agent --workers data_analyst| Option | Description |
|---|---|
--stream |
Enable streaming responses |
--verbose |
Show reasoning/thinking content |
--provider |
LLM provider (anthropic, openai, together, google) |
--model |
Model name override |
--log-level |
Logging level (DEBUG, INFO, WARNING, ERROR) |
--serve |
Start the API server instead of the REPL |
--host |
API server host (default: 127.0.0.1) |
--port |
API server port (default: 8000) |
--multi-agent |
Run supervisor + workers from config.yaml |
--workers |
Filter to specific workers (names from config.yaml) |
--visualize |
Generate Mermaid diagram of agent structure |
You can configure the agent via config.yaml:
llm:
provider: "anthropic"
model: "claude-3-5-sonnet-20240620"
temperature: 0.7
max_tokens: 4096
multi_agent:
supervisor:
provider: "openai"
model: "gpt-5"
workers:
data_analyst:
provider: "together"
model: "Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8"
tools: ["read", "list", "ask_user", "load_dataset", "dataset_info", "dataset_describe", "export_dataset"]Or via environment variables:
LLM_PROVIDER: Default providerANTHROPIC_API_KEY,OPENAI_API_KEY,TOGETHER_API_KEY,GOOGLE_API_KEY: Provider API keysTAVILY_API_KEY: For web search functionalityCODING_AGENT_LOG_LEVEL: Logging level
| Tool | Description |
|---|---|
calculator |
Basic arithmetic operations |
list_directory |
List files in a directory |
read_file |
Read file contents |
write_file |
Write content to files |
run_command |
Execute shell commands (with security restrictions) |
python_repl |
Execute Python code in a sandboxed REPL |
search_web |
Web search via Tavily API |
ask_user |
Request clarification from the user |
Install coding-agent[data] (uv sync --extra data) for XLSX/plot support.
| Tool | Description |
|---|---|
load_dataset |
Load CSV/JSON/JSONL/XLSX into an in-memory dataset registry |
list_datasets |
List loaded datasets |
remove_dataset |
Remove a dataset from memory |
clear_datasets |
Clear all datasets from memory |
dataset_info |
Column types + missing counts |
dataset_head |
First N rows (markdown table) |
dataset_tail |
Last N rows (markdown table) |
dataset_sample |
Random sample of rows |
dataset_describe |
Descriptive stats for numeric columns |
dataset_value_counts |
Value counts for a column |
dataset_select_columns |
Project columns into a new dataset |
dataset_filter |
Row filtering with simple conditions |
dataset_sort |
Sort by one or more columns |
dataset_groupby_agg |
Group-by + aggregate (count/sum/mean/min/max) |
export_dataset |
Export dataset to CSV/JSONL/XLSX (requires confirmation) |
save_histogram_plot |
Save histogram plot (requires confirmation) |
save_scatter_plot |
Save scatter plot (requires confirmation) |
save_bar_plot |
Save bar plot of top value counts (requires confirmation) |
The agent implements defense-in-depth security:
- Path Validation: All file operations are validated against allowed paths to prevent path traversal attacks
- Command Sanitization: Shell commands are inspected for dangerous patterns (rm -rf, sudo, etc.)
- Python Code Inspection: AST-based validation blocks dangerous imports and operations
- Restricted Builtins: Python REPL runs with limited builtins and blocked modules
The ask_user tool supports two modes:
- Interrupt Mode (default): Agent raises
InterruptRequested, control returns to caller - Callback Mode: Uses a callback function for synchronous input
# example: handling interrupts
result = agent.run("help me with something")
while result.is_interrupted:
user_response = input(f"Agent asks: {result.interrupt.question}\n> ")
result = agent.resume(result.interrupt.tool_call_id, user_response)src/coding_agent/
├── agent.py # main CodingAgent class with ReAct loop
├── main.py # CLI entry point and REPL
├── stream_handler.py # streaming response handling
├── logging.py # logging configuration
├── types.py # unified types (UnifiedMessage, ToolCall, etc.)
├── exceptions.py # custom exception hierarchy
├── prompts.py # system prompts and templates
├── clients/ # LLM provider implementations
│ ├── base.py # BaseLLMClient abstract class
│ ├── factory.py # client factory with registry
│ ├── openai_compat.py # shared base for OpenAI-compatible APIs
│ ├── openai.py # OpenAI client
│ ├── together.py # Together AI client
│ ├── anthropic.py # Anthropic/Claude client
│ └── google.py # Google Gemini client
├── tools/ # tool implementations
│ ├── base.py # BaseTool abstract class
│ ├── ask_user.py # human-in-the-loop tool
│ ├── calculator.py # basic math operations
│ ├── data_analysis.py # dataset tools (csv/json/jsonl/xlsx, export, plots)
│ ├── filesystem.py # file operations
│ ├── python_repl.py # Python code execution
│ ├── search.py # web search
│ ├── security.py # path/command/code validation
│ └── system.py # shell command execution
├── multi_agent/ # supervisor/worker orchestration (optional)
│ ├── supervisor.py # supervisor agent
│ ├── worker.py # worker wrapper
│ ├── tools.py # delegate/synthesize tools
│ └── workers/ # worker factories (coder/researcher/reviewer/context/data_analyst)
└── api/ # optional FastAPI server (install with --extra api)
└── utils/
└── stream_parser.py # streaming response parsing
# run all tests
uv run pytest
# run with verbose output
uv run pytest -v
# run specific test file
uv run pytest tests/test_agent.pyuv run ruff check src/
uv run ruff format src/- Create a new client in
clients/inheriting fromBaseLLMClientorOpenAICompatibleClient - Register it in
clients/factory.py:_PROVIDER_REGISTRY["new_provider"] = { "class_path": "coding_agent.clients.new_provider.NewProviderClient", "api_key_env": "NEW_PROVIDER_API_KEY", "default_model": "default-model-name", }
- Create a new tool in
tools/inheriting fromBaseTool - Implement the required properties and
execute()method - Add it to the tools list in
main.py(single-agent CLI) and/or to_create_tools_from_names()for multi-agent config support
class MyTool(BaseTool):
@property
def name(self) -> str:
return "my_tool"
@property
def description(self) -> str:
return "Description of what this tool does"
@property
def parameters(self) -> dict[str, Any]:
return {
"type": "object",
"properties": {
"param1": {"type": "string", "description": "First parameter"}
},
"required": ["param1"]
}
def execute(self, param1: str) -> str:
return f"Result: {param1}"