Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.opper.ai/llms.txt

Use this file to discover all available pages before exploring further.

A coding agent is just a tool-calling loop with the right tools. Give the model read_file, write_file, and run_command, then keep calling it: as long as it returns tool calls, you run them and feed the results back. It writes code, runs it, reads the error, fixes it, and goes again until the task is done. Conversation state persists, so follow-up requests build on what it already did.
python app.py

You › Build a fizzbuzz script and run it for n=15.

Tool › write_file(“fizzbuzz.py”)
→ Wrote 545 bytes

Tool › run_command(“python fizzbuzz.py”)
→ /bin/sh: python: command not found

Tool › run_command(“python3 fizzbuzz.py”)
→ 1 · 2 · Fizz · 4 · Buzz · Fizz · 7 · 8 · Fizz · Buzz · 11 · Fizz · 13 · 14 · FizzBuzz

Bot › Done. Wrote fizzbuzz.py, recovered from the missing python, and verified the output for n=15.

You › Now add a test that fizzbuzz(5) ends with “Buzz”, and run it.

Tool › read_file(“fizzbuzz.py”)
→ def fizzbuzz(n): …

Tool › write_file(“test_fizzbuzz.py”)
→ Wrote 184 bytes

Tool › run_command(“python3 -m unittest -v”)
→ Ran 1 test · OK

Bot › Added test_fizzbuzz.py and it passes. fizzbuzz(5) ends with “Buzz”.

Notice the loop adapting: python wasn’t found, so it retried with python3; on the follow-up it read the existing file before writing the test. You don’t script either move, the model reacts to what the tools return.

The agent

import os, json, subprocess
from openai import OpenAI

client = OpenAI(base_url="https://api.opper.ai/v3/compat", api_key=os.environ["OPPER_API_KEY"])
WORKDIR = "workspace"
os.makedirs(WORKDIR, exist_ok=True)

def read_file(path):
    p = os.path.join(WORKDIR, path)
    return open(p).read() if os.path.exists(p) else f"(no file at {path})"

def write_file(path, content):
    with open(os.path.join(WORKDIR, path), "w") as f:
        f.write(content)
    return f"Wrote {len(content)} bytes to {path}"

def run_command(command):
    r = subprocess.run(command, shell=True, cwd=WORKDIR, capture_output=True, text=True, timeout=30)
    return (r.stdout + r.stderr).strip() or "(no output)"

TOOLS = {"read_file": read_file, "write_file": write_file, "run_command": run_command}

specs = [
    {"type": "function", "function": {"name": "read_file", "description": "Read a file in the workspace.",
        "parameters": {"type": "object", "properties": {"path": {"type": "string"}}, "required": ["path"]}}},
    {"type": "function", "function": {"name": "write_file", "description": "Create or overwrite a file in the workspace.",
        "parameters": {"type": "object", "properties": {"path": {"type": "string"}, "content": {"type": "string"}}, "required": ["path", "content"]}}},
    {"type": "function", "function": {"name": "run_command", "description": "Run a shell command in the workspace and return its output.",
        "parameters": {"type": "object", "properties": {"command": {"type": "string"}}, "required": ["command"]}}},
]

SYSTEM = (
    "You are a coding agent. Use the tools to read, write, and run code in the "
    "workspace until the task is done. Verify your work, then give a short summary."
)

messages = [{"role": "system", "content": SYSTEM}]

def send(user_message):
    messages.append({"role": "user", "content": user_message})
    # Keep going while the model wants to call tools.
    while True:
        r = client.chat.completions.create(model="anthropic/claude-sonnet-4-6", messages=messages, tools=specs)
        msg = r.choices[0].message
        messages.append(msg)
        if not msg.tool_calls:
            return msg.content
        for call in msg.tool_calls:
            args = json.loads(call.function.arguments)
            print(f"  · {call.function.name}")
            messages.append({"role": "tool", "tool_call_id": call.id,
                             "content": TOOLS[call.function.name](**args)})

print(send("Write fizzbuzz.py with a fizzbuzz(n) function, then run it for n=15."))
print(send("Now add a test that fizzbuzz(5) ends with 'Buzz', and run it."))
Run it:
pip install openai
export OPPER_API_KEY="your-api-key"
python app.py

How it works

  • The model drives the loop. Each turn it either calls tools or returns a final answer. You run the tools, feed the results back, and loop while it keeps calling tools, no fixed step count.
  • It recovers on its own. Tool output (including errors) goes straight back to the model, so it adapts: a failed command, a syntax error, a missing file. You don’t script the recovery.
  • State persists. messages holds the whole history, so a follow-up like “now add a test” builds on the code the agent already wrote.
  • Three tools go a long way. read_file, write_file, and run_command are enough to edit and test a project. Add more (search, git, HTTP) and the agent gets more capable. Swap model for any of the 300+ models without touching the loop.
run_command executes shell commands, so sandbox it before pointing it at anything real: a container, a throwaway directory, or an allowlist of commands. The example keeps everything inside a workspace folder.

What’s next

Tool calling

The tool-use round trip this loop is built on.

Agents SDK

A batteries-included agent framework with tools, streaming, and multi-agent.

Conversations

Message history and multi-turn patterns.

Observe

Score the agent’s runs and catch regressions.