For quite a while now, I've had the feeling that AI agents are surrounded by this mystical aura. Nobody really knows what they're doing, they're probably going to take over the world soon, and if you want to build one yourself... well, obviously you need a framework.
What if I told you that's not true? You can build your own AI agent in about 80 lines of code.
I have to admit, this week hasn't been easy. A lot has been happening at work. On top of that, I received an automatic rejection email for one of my CFPs. Normally, that would have occupied my mind for maybe five minutes, because conference rejections are just part of the game.
Except... I had actually been INVITED to that conference. "You're already accepted, just need to collect the talk details in CFP." Because of that invitation, I turned down two other conference opportunities. Oh well. At least I have September free now.😉
Anyway, life goes on. Today is my birthday, so as a little gift from me to myself (and to all of you!), I wrote this article. 😄 I hope you'll enjoy it!
Do We Really Need a Framework?
Let's get to the point.
Frameworks like LangChain, CrewAI, or Mastra aren't doing magic. They simplify things like conversation memory, tool execution, retries, fallbacks, etc.
Once you understand the underlying mechanism, it becomes much easier to decide when a framework is actually worth using. And even if you end up using one anyway, you'll understand what's happening under the hood.
So I decided to build a small demo and see how little code an AI agent actually needs.
I wrote an AI agent in Node.js in roughly 80 lines of code... okay, okay, the core loop is about 80 lines. There are still tools, a provider abstraction, and a bit of surrounding logic. But come on, "an AI agent in 80 lines" sounds much better. 😄
Here's the repository:
https://github.com/sylwia-lask/code-review-agent
And since it's my birthday... if you enjoy the project, feel free to give it a ⭐. Only if you actually like it, of course. 😄
Meet Steve
The application is a simple code review agent. Well... not exactly.
Meet Steve: a software engineer with 15 years of experience reviewing other people's code. Steve doesn't take anything at face value. Sure, he can be a little sarcastic sometimes... but it's hard to argue with his conclusions.
Here's what one of his code reviews looks like:
Or when you have empty diff:
For now, Steve reviews the local Git diff. Which basically means... he's reviewing himself. So I guess I've built a prototype of the famous self-healing agent that, according to @nitsancohen770, is going to take my job one day.
As you can see, I'm basically helping automate myself out of employment. Maybe it's finally time to start thinking about retirement. 😄
"An Agent Is Just a While Loop"
In my previous article, I joked about people claiming that an AI agent is nothing more than a while loop. Well... Mine isn't even a while loop. It's a for loop, because I wanted to protect myself from accidentally creating an infinite loop and loosing too much money on tokens. 😄
Okay, to be fair, the loop itself doesn't actually do anything. It's simply responsible for orchestrating the process. But the funny part is that most agent frameworks are doing something remarkably similar under the hood.
The loop itself was trivial to write. The real challenge was exactly where you'd expect it to be.
Building an AI agent starts with... choosing an LLM. 😄 This time I picked the Gemini API because tokens for projects like this are ridiculously cheap. I'm definitely tempted to build the next version using a local model, but... hold on. 😄
I immediately ran into one issue: some Gemini models were overloaded, so I kept getting 503 responses. That meant I had to implement something frameworks usually provide out of the box — a simple retry mechanism.
Mine is intentionally very basic. Production frameworks usually offer much more, like exponential backoff, jitter, or automatic fallback to another model.
The next challenge was, of course, writing the right prompt. After that, everything became surprisingly straightforward.
How Does the Agent Actually Work?
It's much simpler than you might think.
Step 1: Send the prompt and available tools
We send two things to the model:
- the user's message,
- the list of tools the model is allowed to use.
Here's what the request looks like:
{
"model": "gemini-2.5-flash",
"contents": [
{
"role": "user",
"parts": [
{
"text": "Please review the current git diff."
}
]
}
],
"config": {
"systemInstruction": "You are Steve, a senior software engineer with 15 years of experience...",
"tools": [
{
"functionDeclarations": [
{
"name": "getDiff",
"description": "Get the git diff of the current repository...",
"parameters": {
"type": "OBJECT",
"properties": {},
"required": []
}
},
{
"name": "getFile",
"description": "Read a file from the repository...",
"parameters": {
"type": "OBJECT",
"properties": {
"path": {
"type": "STRING",
"description": "Path to the file relative to the repository root"
}
},
"required": ["path"]
}
},
{
"name": "listFiles",
"description": "List files and directories at a given path...",
"parameters": {
"type": "OBJECT",
"properties": {
"path": {
"type": "STRING",
"description": "Directory path relative to the repository root"
}
},
"required": ["path"]
}
}
]
}
]
}
}
Step 2: Wait for the model's response
The model can respond in one of three ways:
- with plain text,
- by requesting a tool call,
- or with both.
If it returns plain text, we're done: that's our final code review. If it asks us to call a tool, we move on to Step 3.
For example, we might receive:
{
"candidates": [
{
"content": {
"role": "model",
"parts": [
{
"text": "Let's see what damage we're dealing with today..."
},
{
"functionCall": {
"id": "call_001",
"name": "getDiff",
"args": {}
}
}
]
}
}
]
}
Step 3: Execute the tool locally
Now it's our application's turn.
We execute the requested tool locally. For example, running git diff or reading a file from the repository, and send the result back to the model as another message in the conversation.
The tools available in this demo are:
getDiffgetFilelistFiles
In other words, everything a good code reviewer needs. 😄
Step 4: Repeat until the model is done
Then we simply go back to Step 2. To avoid getting stuck in an infinite loop, I limit the maximum number of iterations to 10.
There's one more important detail, though.
Take a look at the next request. Notice that we're sending the entire conversation history back to the model:
{
"contents": [
{
"role": "user",
"parts": [
{
"text": "Please review the current git diff."
}
]
},
{
"role": "model",
"parts": [
{
"text": "Let's see what damage we're dealing with today..."
},
{
"functionCall": {
"id": "call_001",
"name": "getDiff",
"args": {}
}
}
]
},
{
"role": "user",
"parts": [
{
"functionResponse": {
"id": "call_001",
"name": "getDiff",
"response": {
"result": "diff --git a/src/auth.ts b/src/auth.ts\n--- a/src/auth.ts\n+++ b/src/auth.ts\n@@ -12,7 +12,7 @@\n- if (password === storedHash) {\n+ if (password == storedHash) {\n"
}
}
}
]
}
]
}
And... that's it. The LLM decides which tool to use and when it's done. Everything else — executing the tools, handling retries, enforcing iteration limits, and orchestrating the loop — is the responsibility of our application.
Simple, isn't it? If you like visualisations, ChatGPT prepared one for us 😉
What's next?
I'd like to write at least two follow-up articles in the future. One about connecting Steve to MCP, and another about replacing the hosted model with a local LLM.
But... hold on. One step at a time. 😄
Bonus: How does the model know it should call a function?
If you only came here to learn how to build an AI agent, you can probably stop reading now. 😄
This section is for the curious ones. Because sooner or later, someone is going to ask: "Sylwia, what are you talking about? You say you're building your own AI agent, but you're just using the Gemini SDK. You send it JSON, you get JSON back."
And, as @darkwiiplayer often points out, an LLM is basically a very sophisticated next-token predictor, not some magical JSON generator.
So... How does the model know what to do when we send it JSON? What would happen if, instead of Gemini, we used some old local Llama model?
Does the Gemini SDK secretly prepend some special prompt? Or are the models themselves trained for this?
The honest answer is that we don't know all the details. What we do know is that models like Gemini and GPT have native support for tool calling. The SDK is responsible for formatting requests correctly and communicating with the API, while the model itself understands tool declarations and can generate function calls when it decides they're needed.
In other words, if we took an older Llama model, we could still write a prompt explaining how to interpret the JSON and asking it to respond in a specific JSON format.
Then we'd simply call JSON.parse()...
...assuming the model actually returns valid JSON instead of Markdown, an explanation, or a few "helpful" comments it decided to add. 😄
That said, it's worth mentioning that newer open-source models increasingly support tool calling natively as well.
Epilogue: An agent written by another agent
Let's be honest. It's 2026. This agent was written largely by... another AI agent. 😄
Since I've been an AWS Community Builder for a while now, I cancelled my Claude Code subscription and switched to Kiro: an AI IDE with built-in support for multiple LLMs, including Claude, GPT, and Gemini. It costs about the same, but thanks to the program, I get to use it for free.
So far, I'm really enjoying it. It feels a bit like an agent-powered layer on top of VS Code, so I immediately felt at home. I also have the feeling that I'm currently using maybe 10% of what it can actually do, so I'm pretty sure I'll write more about it in the future.
Now I'm just hoping AWS will finally send me some swag. 😄 I'm especially dreaming about one of their T-shirts. Years ago, we actually had a political party in Poland called AWS, so I'd absolutely love to see the expressions on some older people's faces. 😄
AWS... if you're reading this... I'm waiting!! 😄
Final Thoughts
As you can see, an AI agent doesn't have to start with a framework. Sometimes all you need is an LLM, tools, convarsation history and a simple loop. Everything else is convenience, production hardening, and quality-of-life improvements.
So...
What do you think about Steve? And how do you like this approach to building an AI agent? 😄
If you liked this post you can also follow me on LinkedIn.




Top comments (133)
Happy Birthday Sylwia :D
Awwww thanks Francis 🩷🥰🤩☺️
The "Steve reviews himself" moment is the most honest thing in this article — an agent auditing its own diff is exactly where I'd expect the most confident-sounding hallucinations, because the model has both the strongest prior and the least external ground truth. I've seen the same failure mode when static analysis tools try to lint autogenerated code: they flag real patterns as violations because the generated output follows conventions the rule never anticipated. The
forloop cap instead ofwhileis a good instinct too; bounded iteration is one of those things that looks overly cautious until the first time you watch an unbounded agent loop burn through your token quota at 2am.Exactly! 😄 It's a bit like asking an author to review their own book. Of course, it's not quite that extreme here, since my super-simple agent is only reviewing a diff rather than generating the code itself, but the idea is very similar.
And yes, the loop cap turned out to be surprisingly important. Even in a tiny demo, retries and bounded iteration are a must! Especially when you're trying to save a few dollars on tokens by using the cheaper models... like I was. 😂
Hey Sylwia, happy belated birthday! 🎉 Steve reviewing himself was probably my favorite part 😂 It's funny how AI agents seem like magic until you realize it's mostly loops, tool calls, retries... and hoping Gemini doesn't reply with a 503 😄 Great read!
Thank you so much! 😄 Hahaha, maybe it's actually a good thing that Gemini throws a 503 every now and then. 😂 It helps keep us from becoming too optimistic about LLMs. Nothing brings you back to reality faster than your "intelligent agent" suddenly discovering that the model is unavailable. 😄
First of all, happy birthday!! 🎉 The GitHub star is already in place! 😄⭐
Great article, as always, I really enjoyed reading it!
Before I wrote my first agent, I thought it would be some kind of rocket science. But as you wrote, it’s actually pretty simple and straightforward and we don’t really need any special dependencies.
I also have to confirm that using the free Gemini models these days is painful. I encountered a lot of 503 errors and had to switch between models quite often. I think Gemini 2.5 Flash was the most stable one.
This is clearly an AI agent, but have you also noticed that many people now say they’ve created an AI agent even when it’s just a simple prompt file or a skill that runs in VS Code, for example? It feels like everything is an AI agent now. 😄
P.S. Why Steve? Why not Piotr, Krzysztof, or Tomasz? 😄
Thank you so much for the birthday wishes and for the star! 🤩
And yes, exactly! These days, everything is an "AI agent"... just like everything suddenly became "AI-powered." 😂
What's funny is that if you read enough technical articles about agents, one pattern keeps coming up over and over again: our main job is basically to keep the LLM on a very short leash. 😄 Most of the engineering effort goes into making sure it doesn't do something creative at the wrong moment.
And you're absolutely right about Steve! 😂 Why didn't he catch that during his own review? Maybe I should have named him Grzegorz or Przemysław instead. We have much more challenging names for foreigners than Steve. 😄
Exactly! The problem is that we developers couldn’t even keep fully deterministic code under control: bugs everywhere! 😅 And now we have to do it with probabilistic output. It’s going to be quite a ride in the future.
Grzegorz, that’s the one! 😂
Hahaha, exactly! 😂 Maybe Steve needs to retire and make room for a grumpy reviewer from Eastern Europe. 😄 Grzegorz would be a perfect candidate. I might actually have to consider that for the next part of the series!
Okay, now I’m really looking forward to it! I can’t wait to meet the grumpy reviewer Grzegorz! 😂
This resonates deeply — and I think the distinction between 'agent' and 'workflow' is one of the most important conversations we're not having openly enough.
I've been building AgentShare (an MCP server for Solana DeFi data) and running OpenClaw agents on Railway. The hardest part isn't the LLM reasoning — it's designing the control plane that decides when to let the agent roam and when to lock it into a deterministic path.
What I've found: the best agents are not the ones with the most autonomy. They're the ones with the most structured autonomy — clear guardrails, explicit risk caps, and a feedback loop that detects when the agent is drifting off-course.
The dirty secret isn't that demos are fake. It's that real agents are boring to watch. They spend most of their time checking constraints, validating inputs, and logging state. The 'magic' is a tiny fraction of the codebase.
Would love to hear your take on how we, as builders, can be more transparent about this without killing the excitement around agentic systems.
Exactly! 😄 "Agent" sounds incredibly cool, but in practice, the hardest part is usually everything around the agent. Building the actual agent loop is often the easy bit. The real engineering is designing the constraints, the tooling, the validation, and making the whole thing predictable enough that you'd actually trust it.
As for being more transparent without killing the excitement... honestly, I have no idea either. 😂 The more technical articles I read about agentic systems, the less excited I become. Maybe that's inevitable, once you understand how the magic trick works, you start appreciating the engineering instead of the illusion. And I think that's okay. Good engineering is still pretty exciting, just in a different way.
That's beautifully said — 'appreciating the engineering instead of the illusion.' I think that's the mark of a mature builder.
I've noticed the same shift in myself while building OpenClaw agents on Railway. The moment I stopped chasing 'autonomous magic' and started focusing on survivability — retries, state recovery, and deterministic fallbacks — the system actually became more reliable. And ironically, more useful.
The 'magic' is still there, but it's no longer the centerpiece. It's just one component in a system designed to fail gracefully.
Maybe the next phase of agentic systems isn't about better reasoning, but about better forgiveness — designing agents that know when to ask for help, when to pause, and when to admit they don't know. Do you see that as a direction worth exploring?
I really like that last idea too. I think one of the most valuable capabilities an agent can have is knowing when to say, "I don't know," or "I need help." That's not a weakness—it's good engineering.
Maybe that's exactly where we're heading: taking advantage of what LLMs are good at, while surrounding them with deterministic systems that keep everything under control. The intelligence doesn't have to be deterministic, but the system around it should be as much as possible.
I have a feeling this is going to be one of the most interesting areas of software engineering over the next few years. Not because the LLMs themselves will become dramatically smarter overnight, but because we'll get better at building reliable systems around them. 🙂
Happy Birthday, Sylwia 🥳🎂
I simply have to ask: Why "Steve"? Why 15 years of experience? Is that the sweet spot of technical expertise 🤔😂😉
Have a great day!
Thank you so much!
As for Steve... we've already concluded in another comment thread (together with @gramli) that something clearly went wrong there. Steve failed to properly review himself! 😂 So in the next iteration he'll probably be replaced by Grzegorz, a grumpy reviewer from Eastern Europe. 😄
And the "15 years of experience"... well, that was basically me thinking, "Surely if someone has 15 years of experience, they must be wise!" 😂 It's a bit like writing a prompt that says at the end "make no mistakes." You're absolutely save then 🤣
“Surely if someone has 15 years of experience, they must be wise!”
That’s a dangerous conclusion, just look at me. I have roughly 15 years of experience, and I’m about as wise as a blade of grass. 😂😂
Honestly, I could be Grzegorz: a grumpy developer from Eastern Europe, just without the wisdom. 😂
Hahaha, I don't know about that! 😂 In my experience, the people who say they don't know much are often the ones who actually know quite a lot. The really dangerous ones are the people who are absolutely convinced they already know everything. 😄
Happy birthday, Sylwia! 🎂 "Steve reviewing himself" is the funniest line in this...automating yourself out of a job, one diff at a time.
your for loop cap for token safety is the small-scale version of something I've been deep in lately — rate limits and spend caps for agents that live past one session. same instinct, just before it needed to survive a restart.
curious if the MCP follow-up gives Steve any memory across reviews or if he stays stateless on purpose.
Thanks so much! 😄 I couldn't have picked a better birthday project.
Giving Steve memory across reviews is actually a great idea for a future iteration!
And yes, that's exactly how I see the rate limits and spend caps too. It's the same class of problems, just at a different scale. My goal with this little agent was to show that if you understand these challenges in a tiny, 80-line project, you won't be surprised when they show up in much larger, production-grade agent systems. The details change, but the underlying engineering trade-offs are remarkably similar. 🙂
the trade-off doesn't change shape, just the cost of getting it wrong. At 80 lines, a broken loop cap burns a few cents in Gemini tokens.
At production scale, the same missing guardrail means someone else's API key draining overnight. Same problem, bigger blast radius which makes the case for building the tiny version first even stronger, since the mistake stays cheap while you're still finding it...
Exactly! That's why I'd much rather make a mistake on a tiny agent than on a production one. I'd rather discover that my while loop never terminates after burning a few cents than wake up to a production system that's been running all night.
What's also interesting is that my little agent is intentionally harmless. It can only read things, it can't modify files, execute commands, or do anything destructive. And yet, even in that scenario, you immediately have to think about security. What if someone accidentally gives it access to a .env file? Suddenly, even a read-only agent needs guardrails.
So if these concerns already exist in an 80-line toy project, you can imagine how much more important they become once an agent is allowed to perform real actions in production. That's exactly why I think building the tiny version first is so valuable. 🙂
The 80 lines really are the whole thing, which is why I like this post. The part I would push on: your cap of 10 is a cost control, not a correctness one. It fires the same whether Steve solved it in three steps or spent ten re-reading the same file with slightly different reasoning. The cheap upgrade is a progress check: hash the tool name plus normalized arguments per step and stop on repeats. An agent going in circles burns the same budget as one doing work and looks identical in the logs.
Second thing, on read-only being harmless: with getFile and listFiles, repo content is now inside the loop. Read-only protects your filesystem, not Steve's decisions. Text shaped like instructions sitting in a file is a real problem for a code reviewer specifically, because the thing it reads is exactly the thing an attacker controls in a pull request.
Thanks! And yes, that's absolutely true!
If we really started digging into it, we could probably find many, many more issues with my little agent. 😂 But I think that actually reinforces the main point of the article: building the agent itself is usually the easy part. The real challenge begins when you try to tame all that non-deterministic behavior and turn it into something reliable, predictable, and safe.
That's where most of the engineering effort ends up: and where the interesting problems begin. 🙂
Exactly. And the thing that makes that engineering hard is that the ground moves under you: the same input can pass today and fail next week because the model changed, so you cannot lock behavior down with a single assert the way you would with deterministic code. What ends up working is treating the agent like a flaky external service you do not own. You pin behavior with a labeled eval set and thresholds instead of exact equality, run it in CI, and gate releases on the rates moving the right way. The code around the model becomes mostly about making its non-determinism observable and bounded, which is a genuinely different discipline from writing the agent in the first place. Good post, this was a fun thread.
Exactly! And suddenly it turns out that agents aren't taking our jobs just yet. They're mostly giving us a whole new category of engineering problems to solve. 😂
I think many people overcomplicate AI agents by jumping straight into frameworks without understanding the core concepts. The reality is that an agent is often just a well-designed loop with reasoning, tool usage, memory, and decision-making. Frameworks are valuable, but knowing what happens underneath helps developers build better solutions and choose the right tools.
Thanks! 😄 I completely agree. Nobody has ever been worse off for understanding the fundamentals first. These tools and frameworks are still so young that they could change completely another ten times over the next few years. And once you understand those, it's much easier to evaluate new frameworks instead of treating them as magic.
I like this perspective. Frameworks are useful, but understanding the core agent loop first makes you a better developer. Once you know what's happening under the hood, you can choose tools based on need instead of hype.
Absolutely! That was exactly my goal with this article.
Some comments may only be visible to logged-in visitors. Sign in to view all comments.