Just Give the Model a Keyboard
How to build an AI agent from scratch, understand what an agent loop really is, and grow something that modifies its own code.
I remember the feeling I got when writing software first clicked. I went from feeling "so what" about "hello world" to understanding that software engineering is, more than anything else, a pure expression of creation.
As the way we build changes, I'm experiencing that same feeling all over again.
Even though it's been at least a month since I've stopped writing code by hand, it didn't really click that we need to completely relearn the way we approach building software until I went through the "hello world" exercise of SWE in 2026: building an agent loop from scratch.
This guide will give you that same moment.
The Shape of What's Coming
Phase 1: The Bootstrap. You'll direct your coding agent to build the minimal viable seed:
- A loop that talks to a model
- Streaming (because watching tokens appear is satisfying)
- Multi-turn conversation
- Three tools: read file, write file, run shell
- The agentic loop (it keeps going until it decides it's done)
That's it. That's the bootstrap. ~150 lines.
Phase 2: The Handoff. You open a new terminal. You run your agent. You start talking to it instead of your coding agent.
Phase 3: Growing Together. You and your agent build it out together. Add tools. Refactor the architecture. Build a skills system. Watch capability compound.
By the end, you'll have something substantive — and you'll have built it the new way.
Before We Start
What you need:
- Node.js 18+ running in a bash terminal (if you're on Windows, just use WSL)
- A coding agent (Claude Code, Copilot CLI — whatever you use)
- Access to a SOTA reasoning model — and importantly, you can't just use any model. Use the latest, most powerful reasoning model you can get. You can't just use any ol' model and then be like "nuh uh AI is dumb"
Get your model credentials and add them to your shell profile (.zshrc, etc.) — follow your model provider's guidance:
# Example: MiniMax
export MINIMAX_API_KEY="your-api-key"
export MINIMAX_MODEL="MiniMax-M2.5"
# Example: Azure OpenAI
export AZURE_OPENAI_ENDPOINT="your-endpoint"
export AZURE_OPENAI_MODEL_NAME="your-model"
export AZURE_OPENAI_DEPLOYMENT="your-deployment"
export AZURE_OPENAI_API_KEY="your-api-key" Set up the project — open a terminal, cd into wherever you want to create it, start your coding agent, and tell it:
It'll create the directory, initialize npm, install dependencies, set up tsconfig. That's its job.
You're ready.
Phase 1: How to Build an Agent Loop
You're going to direct your coding agent through 5 milestones. After each one, restart so that you're running the latest version of your agent.
Milestone 1: First Contact — Calling the Model
Open the fresh new agent.ts so you can watch your code generate in realtime. Then, tell your coding agent:
The shape:
import { OpenAI } from "openai"
const client = new OpenAI({ /* endpoint, apiKey from env */ })
async function main() {
const response = await client.chat.completions.create({
model: deploymentName,
messages: [{ role: "user", content: "Say hello in exactly 5 words." }]
})
console.log(response.choices[0].message.content)
} Test it: cd BestAgentEver and then npm run dev.
You should see something like: Hello there, how are you?
Milestone 2: Streaming Responses
Batched responses feel dead. Streaming feels alive. Prompt:
The shape:
const stream = await client.chat.completions.create({
model: deploymentName,
messages: [...],
stream: true
})
for await (const chunk of stream) {
const content = chunk.choices[0]?.delta?.content
if (content) process.stdout.write(content)
} Test it: rerun your agent and watch the tokens appear one by one. That's the feeling we want.
Milestone 3: Multi-turn Conversation
An agent that can't hold a conversation isn't an agent.
The shape:
type Message = { role: "user" | "assistant", content: string }
const messages: Message[] = []
// readline setup for interactive prompt
while (true) {
const input = await prompt("You: ")
if (input === "exit") break
messages.push({ role: "user", content: input })
const response = await streamChat(messages)
messages.push({ role: "assistant", content: response })
} Test it:
You: What's 2 + 2?
Agent: 4
You: What did I just ask you?
Agent: You asked what 2 + 2 equals. It remembers. But it can't do anything yet.
Milestone 4: Adding Tools — Read, Write, Execute
Here's the insight that makes everything else possible:
Read file + Write file + Run shell = universal basis.
With these three tools, a model can do almost anything a developer can do. Including modify its own code.
The shape:
const tools = [{
type: "function",
function: {
name: "read_file",
description: "Read the contents of a file",
parameters: {
type: "object",
properties: { path: { type: "string" } },
required: ["path"]
}
}
}, /* write_file, shell similarly */]
function executeTool(name: string, args: Record<string, string>): string {
switch (name) {
case "read_file": return fs.readFileSync(args.path, "utf-8")
case "write_file": fs.writeFileSync(args.path, args.content); return "ok"
case "shell": return execSync(args.command, { encoding: "utf-8" })
}
} Test it:
You: What files are in this directory?
Agent: [calls shell with ls]
[Result: src/, package.json, ...] It can see and act. But notice — after calling tools, it stops. It doesn't process results and respond. We need the loop.
Milestone 5: The Agentic Loop — How AI Agents Decide When They're Done
This is where it becomes an agent.
The shape:
async function runAgentLoop(): Promise<void> {
while (true) {
const response = await streamChatWithTools(messages)
messages.push(response.assistantMessage)
if (response.toolCalls.length === 0) break
for (const tc of response.toolCalls) {
const result = executeTool(tc.name, tc.args)
messages.push({ role: "tool", tool_call_id: tc.id, content: result })
}
// loop continues — model sees results and decides next action
}
} Test it:
You: Find all .ts files, read agent.ts, and tell me how many lines it has
Agent: [calls shell: ls *.ts]
[calls read_file: agent.ts]
Agent: src/agent.ts has 207 lines. It called two tools, synthesized the results, and responded. It decided when it was done.
That's the agent loop. That's the whole primitive.
Phase 2: The Handoff
Stop.
Look at what you have: ~200 lines of TypeScript. An agent that can read files, write files, run shell commands, and decide when it's done.
Those capabilities are sufficient for it to modify itself.
Open a fresh terminal. Navigate to your project directory. Run your agent: npm run dev
From this moment on, you're not talking to Claude Code or Cursor or whatever you've been using. You're talking to your agent.
Phase 3: How AI Agents Grow Themselves
Everything from here happens in conversation with your agent.
First Growth: A New Tool
Let's see if it can add a capability to itself.
Watch it:
- Read its own code
- Understand the tool pattern
- Add the new tool definition and implementation
- Write the file
- Give you testing instructions
Restart your agent, and then prompt it: what's in the src folder?
It works. Your agent just added a capability to itself.
Second Growth: Improve the UX
Maybe you've noticed — UX could be better! API calls silently fail, you can type random characters while the agent is working, and who doesn't love a loading spinner.
Restart and test it:
You: What files are in this directory?
● Thinking... Third Growth: Architecture Improvement
The tools are getting unwieldy in a switch statement.
Watch it refactor itself. Restart. Verify it still works.
Fourth Growth: A System Prompt + Proactivity
Right now the agent has no identity, guidance, or sense of purpose.
Restart. Feel the difference. It should feel like you've transitioned from building an agent to building your agent.
Fifth Growth: Skills
Here's where it gets even more interesting.
Watch it build a skills system. Restart. Tell it:
You: list skills
You: load code-review then review agent.ts Your agent now has a skills system. And it built it itself. And then used said skill to review its own code.
Sixth Growth: Let Skills Build Tools
This is the compounding part.
A skill that teaches the agent to make tools. Tools and skills building each other.
This is the shape of compounding capability.
Keep Going
From here, the possibilities are yours:
- Add a tool for web search
- Add a skill for writing tests
- Add a tool for git operations
- Add a skill for debugging
- Add a tool for running TypeScript and capturing output
Each capability makes the next one easier to add.
What Just Happened
You started with nothing.
You directed a coding agent to build a 150-line bootstrap. Then you switched to your creation and grew it through conversation. It added tools. It refactored its own architecture. It built a skills system. It used skills to build itself more tools.
The loop is the primitive. Read, write, and execute are the universal basis. Everything else — every feature, every capability — is emergent from the model's ability to use those primitives on its own codebase.
This is the paradigm shift:
You didn't build a product. You grew one.
The way we build software changed as soon as models became capable enough to modify their own code based on natural language direction. That happened recently. Most people haven't caught up yet.
Now you have.
What's Next
Your agent is real, but it's also minimal. If you want to go deeper:
- Conversation persistence — save and resume
- A real TUI, or deploy your agent somewhere — make it feel like an application
- Study production implementations — Claude Code, pi-agent, OpenClaw
And honestly? The most important thing is to keep using what you built. Talk to it. Grow it. See how far you can go together.
That's the new workflow. This is how we build agents now.
Frequently Asked Questions
What is an agent loop?
An agent loop is the core cycle that powers AI agents. The model reads context, decides on an action (like reading a file or running a shell command), executes it, observes the result, and repeats — continuing until the task is complete. The loop is what turns a stateless language model into an autonomous agent that can accomplish multi-step tasks.
How do AI agents work?
AI agents work by combining a large language model with tools inside a loop. The model receives a prompt and conversation history, decides which tool to call (or whether to respond directly), gets the tool's result, and loops back. Three tools form a universal basis: read file, write file, and run shell command. With just these primitives, an agent can do almost anything a developer can — including modify its own source code.
How many lines of code does it take to build an AI agent?
The core bootstrap — streaming, multi-turn conversation, three tools, and the agentic loop — is roughly 150 lines of TypeScript. That's enough for a fully functional agent that can read, write, execute, and decide when it's done. Everything beyond that (skills, UX, architecture) is grown incrementally through conversation with the agent itself.
What tools does an AI agent need?
At minimum, three: read file, write file, and run shell command. These form a universal basis — with file I/O and shell access, a model can install packages, run tests, query APIs, modify code, and essentially perform any task a developer would do from a terminal. Additional tools (web search, git operations, etc.) can be added later, and the agent can even add tools to itself.
This tutorial became a framework.
The 150-line bootstrap grew into ouroboros — a zero-dependency TypeScript harness for agents that self-modify, remember, and grow.