TheFocus.AI TheFocus.AI
01 setup Lesson 2

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

Agent Integration

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.

Please tutor me in this lesson using the following context. First, read the instructions at: https://courses.thefocus.ai/llms.txt My Student ID is: <none> The lesson markdown source is at: https://courses.thefocus.ai/build-ai-coding-agent/01-setup/02-bootstrap.md

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

  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 itbun 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:

ProblemFix
401 UnauthorizedCheck your OPENROUTER_API_KEY in .env
Model not foundCheck AGENT_MODEL in .env — try google/gemini-3-pro-preview
bun: command not foundRun mise use bun again, restart terminal
Empty response / hangThe model might be rate-limited. Wait a minute and try again.
JSON parse errorThe 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 ConceptFinal Location
System promptsrc/prompts/default.md
bash toolsrc/tools/system.ts + src/tools/index.ts
API callsrc/lib/api.ts (callLLM)
Agent loopsrc/agent.ts (runTurn)
REPLsrc/index.ts

← Chapter 01 · Next: Chapter 03 →

Previous Lesson 2 of 11 Next