Chapter 02: The Bootstrap Agent
Run the smallest possible coding agent — a 50-line script that captures the core of what an agent is.
TUTOR WITH THEFOCUS.AI
Copy this prompt into Claude, ChatGPT, or any external AI assistant. It points the assistant to the course instructions and links it to your student profile to track your progress and customize observations.
You are not enrolled yet. Enroll to generate a Student ID to track lesson completions and store learning notes.
Chapter 02: The Bootstrap Agent
What We’re Building
We’re going to run the smallest possible coding agent — a 50-line TypeScript script that demonstrates the core agent loop. It has:
- A system prompt telling the LLM what it is
- A single
bashtool that lets it run commands on your machine - A REPL loop where you type, it thinks, it responds
Step 1: Get the Bootstrap Script
Copy the smallest coding agent into bootstrap.ts in your project directory.
The script is a simple loop:
1. User types input
2. Send input + system prompt to the LLM via OpenRouter
3. If the LLM returns a tool call (bash), run it and send results back
4. If the LLM returns text, show it to the user
5. Repeat
The Key Parts
The System Prompt tells the model who it is and what tools it has:
const systemPrompt = `You are a helpful AI assistant with access to a bash tool.
When you need to run a command, use the bash tool.
Be concise and helpful.`;
The Tool Definition describes the bash function to the model:
const tools = [
{
type: "function",
function: {
name: "bash",
description: "Run a bash command",
parameters: {
type: "object",
properties: {
command: { type: "string" },
},
required: ["command"],
},
},
},
];
The API Call sends messages to OpenRouter:
const response = await fetch("https://openrouter.ai/api/v1/chat/completions", {
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: MODEL,
messages: history,
tools: tools,
}),
});
The Loop handles tool calls:
while (true) {
const response = await callLLM(messages);
const msg = response.choices[0].message;
if (msg.tool_calls) {
// Execute each tool call
for (const tc of msg.tool_calls) {
const result = await runBash(tc.function.arguments.command);
messages.push({ role: "tool", content: result, tool_call_id: tc.id });
}
messages.push({ role: "assistant", tool_calls: msg.tool_calls });
} else {
// Final text response
console.log(msg.content);
break;
}
}
Step 2: Run It
bun run ./bootstrap.ts
You should see the agent start and wait for input. Try your first command:
what files are in this directory?
You’ll see something like:
$ ls -F
bootstrap.ts
mise.toml
STEPS.md
The files in the current directory are:
- bootstrap.ts
- mise.toml
- STEPS.md
If you see the model reasoning before the command (especially with Gemini), that’s the reasoning_details field — we’ll use it later.
Try These Commands
create a new file called hello.txt with "Hello World" in it
read the contents of hello.txt
what does git status show?
Step 3: Understand What Just Happened
Here’s what the loop actually did:
- Your input → “what files are in this directory?”
- Sent to LLM → System prompt + your message
- LLM thinks → “I need to run
lsto see the files” - LLM outputs tool call →
{ "function": { "name": "bash", "arguments": {"command": "ls -F"} } } - Your code runs it →
bunshell runsls -F, returns the output - Output sent back to LLM → “bootstrap.ts\nmise.toml\nSTEPS.md”
- LLM responds → “The files in the current directory are…”
The LLM never ran ls. It never touched your filesystem. It just output a JSON object that said “hey, run this command and tell me what happens.”
Step 4: Debugging
If something goes wrong, here are the most likely issues:
| Problem | Fix |
|---|---|
401 Unauthorized | Check your OPENROUTER_API_KEY in .env |
Model not found | Check AGENT_MODEL in .env — try google/gemini-3-pro-preview |
bun: command not found | Run mise use bun again, restart terminal |
| Empty response / hang | The model might be rate-limited. Wait a minute and try again. |
| JSON parse error | The model returned invalid JSON. Just retry — this gets better with better models. |
What We Just Built
Congratulations. You have a working AI coding agent. It’s primitive — one tool, one prompt, no persistence — but it’s a real agent. Everything else we build will improve this core loop:
- Better tools (instead of just bash)
- Better prompts (split into files, multiple agents)
- Better observability (cost tracking, logging)
- Better architecture (subagents, skills)
Source Code Reference
The bootstrap script evolves into the full project. Here’s where the key pieces end up:
| Bootstrap Concept | Final Location |
|---|---|
| System prompt | src/prompts/default.md |
| bash tool | src/tools/system.ts + src/tools/index.ts |
| API call | src/lib/api.ts (callLLM) |
| Agent loop | src/agent.ts (runTurn) |
| REPL | src/index.ts |