s01
The Agent Loop
Tools & ExecutionBash is All You Need
123 LOC1 toolsSingle-tool agent loop
The minimal agent kernel is a while loop + one tool
[ s01 ] s02 > s03 > s04 > s05 > s06 | s07 > s08 > s09 > s10 > s11 > s12
"One loop & Bash is all you need" -- one tool + one loop = an agent.
Harness layer: The loop -- the model's first connection to the real world.
Problem
A language model can reason about code, but it can't touch the real world -- can't read files, run tests, or check errors. Without a loop, every tool call requires you to manually copy-paste results back. You become the loop.
Solution
+--------+ +-------+ +---------+
| User | ---> | LLM | ---> | Tool |
| prompt | | | | execute |
+--------+ +---+---+ +----+----+
^ |
| tool_result |
+----------------+
(loop until stop_reason != "tool_use")
One exit condition controls the entire flow. The loop runs until the model stops calling tools.
How It Works
- User prompt becomes the first message.
messages.push({ role: "user", content: query });
- Send messages + tool definitions to the LLM.
const response = await client.messages.create({
model: MODEL, system: SYSTEM, messages,
tools: TOOLS, max_tokens: 8000,
});
- Append the assistant response. Check
stop_reason-- if the model didn't call a tool, we're done.
messages.push({ role: "assistant", content: response.content });
if (response.stop_reason !== "tool_use") {
return;
}
- Execute each tool call, collect results, append as a user message. Loop back to step 2.
const results: Anthropic.Messages.ToolResultBlockParam[] = [];
for (const block of response.content) {
if (block.type === "tool_use") {
const input = block.input as { command: string };
const output = runBash(input.command);
results.push({
type: "tool_result",
tool_use_id: block.id,
content: output,
});
}
}
messages.push({ role: "user", content: results });
Assembled into one function:
async function agentLoop(messages: Anthropic.Messages.MessageParam[]): Promise<void> {
while (true) {
const response = await client.messages.create({
model: MODEL, system: SYSTEM, messages,
tools: TOOLS, max_tokens: 8000,
});
messages.push({ role: "assistant", content: response.content });
if (response.stop_reason !== "tool_use") {
return;
}
const results: Anthropic.Messages.ToolResultBlockParam[] = [];
for (const block of response.content) {
if (block.type === "tool_use") {
const input = block.input as { command: string };
const output = runBash(input.command);
results.push({
type: "tool_result",
tool_use_id: block.id,
content: output,
});
}
}
messages.push({ role: "user", content: results });
}
}
That's the entire agent in under 30 lines. Everything else in this course layers on top -- without changing the loop.
What Changed
| Component | Before | After |
|---|---|---|
| Agent loop | (none) | while (true) + stop_reason |
| Tools | (none) | bash (one tool) |
| Messages | (none) | Accumulating list |
| Control flow | (none) | stop_reason !== "tool_use" |
Try It
cd learn-claude-code-ts
bun run agents/s01_agent_loop.ts
Create a file called hello.ts that prints "Hello, World!"List all TypeScript files in this directoryWhat is the current git branch?Create a directory called test_output and write 3 files in it