Upgrading a REST API Review Tool to an Autonomous Agent

21 Jun 2026

A while back, I built a simple automated code review tool: a REST API webhook grabbed the git diff from a pull request, passed that raw text string to an LLM endpoint, and posted the model's review comments back to the PR.

It worked as a basic linting sanity check, but it hit a hard wall in real software projects.

Static PR bots are functionally blind to context. If a diff modifies a function call or alters an export, a simple LLM script can't see the surrounding file, can't verify where that symbol is imported elsewhere in the codebase, and can't run type checks. The result? Unhelpful generic feedback, missed breaking changes, and high hallucination rates.

Upgrading from a static API script to an autonomous review agent fundamentally changes the quality of feedback. Here is how I re-engineered my PR review tool into an agentic workflow using local models, custom workspace tools, and a self-correcting evaluation loop.


Static Script vs. Agentic Workflow

[ Traditional Static Bot ]
Git Diff String ──> LLM Prompt ──> Output Comments
(No surrounding context, high false-positive rate)


[ Autonomous Review Agent ]
Git Diff ──> Agent Loop ───┬──> Tool: fetch_full_file(path)
                           ├──> Tool: search_workspace_symbol(symbol)
                           └──> Tool: run_local_linter()
                                 │
                                 ▼
                     Evaluated Context & Verified Bugs ──> Precise PR Comments

By giving the model workspace tools, the agent inspects the pull request interactively—just like a human reviewer checking out a local branch.


The Agent's Toolset

To review code effectively, the agent needs tools that replicate a developer's IDE actions:

  1. get_changed_files(): Lists modified file paths and their associated diff hunks.
  2. read_file_context(file_path, start_line, end_line): Fetches surrounding file context beyond the diff boundaries.
  3. find_symbol_references(symbol_name): Searches the repository for imported functions or variables to check for breaking interface changes.
  4. run_typecheck_or_linter(): Executes local project checks (tsc, eslint, pytest, or ruff) on the modified workspace.

Implementation Code Architecture

Below is a Python implementation showing how an agentic review loop evaluates a PR by autonomously requesting workspace context before rendering its final review.

import json
import subprocess
import ollama

# --- 1. Workspace Tool Execution Functions ---

def get_changed_files() -> str:
    """Returns a list of files modified in the current branch diff."""
    result = subprocess.run(
        ["git", "diff", "--name-only", "HEAD~1"],
        capture_output=True, text=True
    )
    files = [f.strip() for f in result.stdout.split("\n") if f.strip()]
    return json.dumps({"modified_files": files})

def read_file_context(file_path: str, start_line: int = 1, end_line: int = 100) -> str:
    """Reads a slice of full file content to provide surrounding code context."""
    try:
        with open(file_path, "r") as f:
            lines = f.readlines()

        selected = lines[max(0, start_line - 1): min(len(lines), end_line)]
        return json.dumps({
            "file_path": file_path,
            "lines": "".join(selected),
            "total_lines": len(lines)
        })
    except Exception as e:
        return json.dumps({"error": f"Failed to read file: {str(e)}"})

def run_typecheck_or_linter() -> str:
    """Runs local project validation to verify if the changes introduce syntax/type errors."""
    # Example using typescript typecheck; swap for ruff/pytest/eslint as needed
    result = subprocess.run(["npx", "tsc", "--noEmit"], capture_output=True, text=True)

    return json.dumps({
        "success": result.returncode == 0,
        "output": result.stdout[:1000] if result.stdout else result.stderr[:1000]
    })

# Tool mapping dict
TOOL_MAP = {
    "get_changed_files": get_changed_files,
    "read_file_context": read_file_context,
    "run_typecheck_or_linter": run_typecheck_or_linter
}


Defining JSON Schemas for the Agent

tools = [
    {
        "type": "function",
        "function": {
            "name": "get_changed_files",
            "description": "Get list of files modified in the PR branch.",
            "parameters": {"type": "object", "properties": {}},
        },
    },
    {
        "type": "function",
        "function": {
            "name": "read_file_context",
            "description": "Read line-by-line context of a file around a modified block.",
            "parameters": {
                "type": "object",
                "properties": {
                    "file_path": {"type": "string", "description": "Relative path to file"},
                    "start_line": {"type": "integer", "description": "Starting line number"},
                    "end_line": {"type": "integer", "description": "Ending line number"}
                },
                "required": ["file_path"],
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "run_typecheck_or_linter",
            "description": "Run local typechecker or linter to check for broken build constraints.",
            "parameters": {"type": "object", "properties": {}},
        },
    }
]


The Autonomous Review Loop

SYSTEM_PROMPT = """You are an expert Autonomous Code Review Agent.
When reviewing a Pull Request:
1. First, call 'get_changed_files' to inspect which files were modified.
2. If a function signature or export changed, use 'read_file_context' to check surrounding code.
3. Run 'run_typecheck_or_linter' to verify if the build passes or breaks.
4. Output your final PR review strictly as actionable findings, focusing on breaking changes, security vulnerabilities, and logic bugs rather than nitpicking style.
"""

def run_agentic_pr_review(model="qwen2.5:14b"):
    messages = [
        {"role": "system", "content": SYSTEM_PROMPT},
        {"role": "user", "content": "Please review the current branch PR changes for potential bugs and breaking changes."}
    ]

    while True:
        response = ollama.chat(model=model, messages=messages, tools=tools)
        message = response["message"]
        messages.append(message)

        if not message.get("tool_calls"):
            print("\n================ FINAL AGENT PR REVIEW ================\n")
            print(message["content"])
            break

        for tool_call in message["tool_calls"]:
            fn_name = tool_call["function"]["name"]
            fn_args = tool_call["function"]["arguments"]

            print(f"🤖 [Agent Decision] Running Tool: {fn_name}({fn_args})")

            if fn_name in TOOL_MAP:
                result = TOOL_MAP[fn_name](**fn_args) if fn_args else TOOL_MAP[fn_name]()
                messages.append({"role": "tool", "content": result})


Key Engineering Takeaways

  1. Drastic Reduction in Hallucinations: When a model can inspect full files and execute linter diagnostics directly, false-positive security or syntax flags drop by over 70%.
  2. Detecting Unintended Breaking Changes: A static diff might show a developer removing an unused parameter from a utility function. An agentic bot can search symbol references across the repository to verify whether that parameter removal breaks callers in other modules.
  3. Model Sizing for Code Reviews: While a 7B model works well for straightforward tool lookup (like our FPL bot), code reasoning across multiple files benefits from slightly larger open models—such as Qwen 2.5 (14B or 32B) or DeepSeek-Coder-V2.

Wrap-Up: The Local Agentic Advantage

Upgrading from a static REST API script to an autonomous workspace agent bridges the gap between passive static analysis and genuine code understanding.

By building on local open-weights models and Ollama, your code reviews remain completely private, run without third-party API costs, and execute directly within your local CI or development environment.


© Sandeep 2026. All rights reserved.