Clawdbot (or openclaw in the last few days) has taken the world by storm, in some ways this feels like the ChatGPT moment for agentic AI i.e. ai that acts not just responds to your questions.
How does clawdbot, or even better AI that acts, works? Lets implement it in as few lines of code as possible to uncover the mystery 🎩 Here are the ingredients
🧠 AI (LLM)
⚒️ Tools
🔁 Agentic loop
🗒️ Memory
🪖 Rules aka prompt
Let’s start with the easy part, calling the AI, the one that responds but does not act.
def call_llm(messages, tools, api_key):
req = urllib.request.Request(
"<https://openrouter.ai/api/v1/chat/completions>",
data=json.dumps({"model": "openrouter/free", "messages": messages, "tools": tools}).encode(),
headers={"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"},
method="POST"
)
with urllib.request.urlopen(req) as resp:
data = json.loads(resp.read().decode())
if "choices" not in data:
return f"Error: {data.get('error', 'No choices')}", []
msg = data["choices"][0]["message"]
return msg.get("content", ""), msg.get("tool_calls", [])
this is the least exciting part but its the one doing most of the heavy lifting lets not forget, we are sending our conversation in messages , the tools available and an API KEY to our provide of choice, in this case openrouter which gives us access to any model we want and get back a response together with tools the AI would like to use.
The tools are what allows the AI to act 🪄 so its part of the magic for sure. Tools are not new, in fact they date back to GPT4 which launched a few months after ChatGPT, but it has taken some time for the dust to settle in making the AI use tools more reliably. Introducing tools is a double edge sword ⚔️ On the one hand, adding more tools makes the AI more able to do more things, so the more the better in some sense, but on the other hand as we give it more tools we make it harder for the AI to pick the right one and we experience that with worse performance after a point. So there is a minimalist philosophy that has prevailed in regards to tools where we add the minimum tools needed to unlock AI abilities. In our case, these are read, write and edit files as well as run terminal commands and search the web through exa.
def read(path: str) -> str:
return Path(path).read_text()
def write(path: str, content: str) -> str:
Path(path).parent.mkdir(parents=True, exist_ok=True)
Path(path).write_text(content)
return f"Written to {path}"
def edit(path: str, old: str, new: str) -> str:
content = Path(path).read_text()
Path(path).write_text(content.replace(old, new, 1))
return f"Edited {path}"
def run(command: str) -> str:
result = subprocess.run(command, shell=True, capture_output=True, text=True)
return result.stdout or result.stderr
def web_search(query: str) -> str:
import urllib.request, urllib.parse, json, os
api_key = os.environ.get("EXA_API_KEY")
if not api_key:
return "Error: EXA_API_KEY not set"
data = json.dumps({"query": query, "num_results": 5}).encode()
req = urllib.request.Request(
"<https://api.exa.ai/search>",
data=data,
headers={"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}
)
with urllib.request.urlopen(req) as resp:
results = json.loads(resp.read().decode()).get("results", [])
if not results:
return "No results found."
return "\\n\\n".join(f"- {r['title']}\\n {r['url']}" for r in results)
TOOLS = {"read": read, "write": write, "edit": edit, "run": run, "web_search": web_search}
TOOL_SCHEMAS = [
{"type": "function", "function": {"name": "read", "parameters": {"type": "object", "properties": {"path": {"type": "string"}}, "required": ["path"]}}},
{"type": "function", "function": {"name": "write", "parameters": {"type": "object", "properties": {"path": {"type": "string"}, "content": {"type": "string"}}, "required": ["pa
th", "content"]}}},
{"type": "function", "function": {"name": "edit", "parameters": {"type": "object", "properties": {"path": {"type": "string"}, "old": {"type": "string"}, "new": {"type": "strin
g"}}, "required": ["path", "old", "new"]}}},
{"type": "function", "function": {"name": "run", "parameters": {"type": "object", "properties": {"command": {"type": "string"}}, "required": ["command"]}}},
{"type": "function", "function": {"name": "web_search", "parameters": {"type": "object", "properties": {"query": {"type": "string"}}, "required": ["query"]}}},
]