---
title: "Chapter 02: The Bootstrap Agent"
description: "Run the smallest possible coding agent — a 50-line script that captures the core of what an agent is."
type: lesson
order: 2
chapter: "01-setup"
---

# 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 `bash` tool 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](https://thefocus.ai/recipes/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:

```typescript
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:

```typescript
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:

```typescript
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:

```typescript
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

```bash
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:

1. **Your input** → "what files are in this directory?"
2. **Sent to LLM** → System prompt + your message
3. **LLM thinks** → "I need to run `ls` to see the files"
4. **LLM outputs tool call** → `{ "function": { "name": "bash", "arguments": {"command": "ls -F"} } }`
5. **Your code runs it** → `bun` shell runs `ls -F`, returns the output
6. **Output sent back to LLM** → "bootstrap.ts\nmise.toml\nSTEPS.md"
7. **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`                               |

---

[← Chapter 01](/build-ai-coding-agent/01-setup/01-environment/) · [Next: Chapter 03 →](/build-ai-coding-agent/01-setup/03-scaffolding/)
