Building CLI Agents with Python and Ollama

cli agentscommand-line interfacellmlocal aiollamapythonsubprocessterminal automationtool calling

Introduction

The Command-Line Interface (CLI), also known as the terminal or shell, is a text-based interface for viewing, managing, and interacting with your computer by typing specific commands. CLI AI agents represent the next evolution in developer tooling, merging the power of large language models (LLMs) with direct access to your machine's terminal, files, and external tools. Imagine harnessing the intelligence of the most advanced AI models directly in your terminal, without the need for a web interface—that's exactly what CLI agents deliver.

The release of ChatGPT in late 2022 transformed how users interact with software, enabling natural language input instead of rigid commands. In 2023, OpenAI introduced a groundbreaking feature: tool calling. This allowed models to move beyond text generation, sending structured requests to applications, receiving results, and continuing reasoning. By 2025, the landscape matured with the arrival of Super CLI, the first agent explicitly designed for terminal-only operation, followed closely by Anthropic's Claude Code and OpenAI's Codex CLI. Looking ahead to 2026, CLI agents are becoming integral to developer workflows, with a growing emphasis on hybrid models that balance local execution with cloud-based intelligence.

The success of these agents stems from their seamless integration into existing developer habits. Rather than replacing the terminal, they augment it with reasoning capabilities. The CLI has evolved from a human-driven command executor into a collaborative workspace where users set objectives, and AI handles much of the execution—from file manipulation to system administration.

Today, three primary types of CLI agents exist:

  • Cloud-native (e.g., Claude Code): The LLM runs in the cloud, while the CLI securely accesses local tools and files.
  • Open-source (e.g., Hermes): The orchestration framework is open-source, supporting both local and remote models.
  • Fully-local (e.g., Ollama): Both the model and orchestration run entirely on your machine, ensuring privacy and no network dependency.

In this tutorial, I'll guide you through building a fully-local CLI agent using Python and Ollama. We'll write and explain every line of code, making it easy to replicate and customize for your own needs.

Setting Up Your Environment

First, install Ollama, the most popular library for running open-source LLMs locally, using pip:

pip install ollama==0.6.2

Next, download Ollama from the official website and then pull your chosen model. I recommend Alibaba's Qwen 2.5 for its balance of intelligence and efficiency—perfect for local execution.

ollama pull qwen2.5

After the model is ready, you can start coding in Python:

import ollama

llm = "qwen2.5"

Defining the Tool: Shell Command Execution

To turn your agent into a practical CLI assistant, it needs the ability to execute shell commands. We'll use Python's built-in subprocess module to run system commands directly from your code.

import subprocess

# 1. Define the actual tool function
def execute_shell_command(command: str) -> str:
    """Executes a terminal command and returns stdout or stderr."""
    try:
        result = subprocess.run(command, shell=True, capture_output=True, text=True, timeout=30)
        if result.returncode == 0:
            return result.stdout
        else:
            return f"Error (exit code {result.returncode}): {result.stderr}"
    except subprocess.TimeoutExpired:
        return "Error: Command timed out after 30 seconds."
    except Exception as e:
        return f"Unexpected error: {str(e)}"

This function takes a command string, executes it in the shell, and returns the output or an error message. The timeout parameter prevents runaway processes, and capturing stderr ensures you get feedback on failures.

Mapping Tools for Ollama

Ollama's tool calling API requires function schemas in a specific JSON format. We'll define a schema for our shell executor:

tool_schema = {
    "type": "function",
    "function": {
        "name": "execute_shell_command",
        "description": "Execute a shell command on the local system and return the output.",
        "parameters": {
            "type": "object",
            "properties": {
                "command": {
                    "type": "string",
                    "description": "The shell command to execute."
                }
            },
            "required": ["command"]
        }
    }
}

Building the Agent Loop

The agent operates in a loop: it sends the user's request and conversation history to the model, checks if the model wants to call a tool, executes it, and feeds the result back. Here’s a complete implementation:

def run_cli_agent(user_query, max_iterations=5):
    messages = [{"role": "user", "content": user_query}]
    
    for _ in range(max_iterations):
        response = ollama.chat(
            model=llm,
            messages=messages,
            tools=[tool_schema]
        )
        
        # Incrementally build the assistant's response
        messages.append(response['message'])
        
        # Check if the model requested a tool call
        if response['message'].get('tool_calls'):
            for tool_call in response['message']['tool_calls']:
                if tool_call['function']['name'] == 'execute_shell_command':
                    cmd = tool_call['function']['arguments']['command']
                    print(f"Executing: {cmd}")
                    output = execute_shell_command(cmd)
                    
                    # Append tool result to conversation
                    messages.append({
                        "role": "tool",
                        "content": output,
                        "name": "execute_shell_command"
                    })
        else:
            # No tool call, so print the final response and break
            print("Assistant:", response['message']['content'])
            break
    else:
        print("Exceeded maximum iterations without a final response.")

This loop ensures the agent can handle multi-step tasks, like checking a file and then editing it, by iteratively calling tools until it produces a text response.

Putting It All Together

Now you can run your agent with a simple call:

if __name__ == "__main__":
    query = "List all files in the current directory and show their sizes."
    run_cli_agent(query)

This will prompt the model to execute ls -la or equivalent commands, then summarize the results.

Enhancing Your Agent

To make your agent more robust, consider these improvements:

  • Add more tools for file reading, writing, or web requests.
  • Implement error recovery by catching exceptions and prompting the model to adapt.
  • Introduce conversation memory using persistent storage or session files.
  • Respect system safety by adding a confirmation step for destructive commands.

Conclusion

You've just built a fully-local CLI agent with Python and Ollama, capable of executing shell commands based on natural language instructions. This foundation is easily extendable to suit your specific workflow, whether you're automating system administration, file management, or development tasks. As we move further into 2026, such agents will become indispensable tools for developers, bridging the gap between human intent and machine execution.

Now it's your turn—experiment, extend, and let your terminal think for you.

via Towards Data Science

Related