惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

月光博客
月光博客
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
人人都是产品经理
人人都是产品经理
IT之家
IT之家
Cyberwarzone
Cyberwarzone
T
Troy Hunt's Blog
有赞技术团队
有赞技术团队
阮一峰的网络日志
阮一峰的网络日志
T
Threat Research - Cisco Blogs
S
SegmentFault 最新的问题
Apple Machine Learning Research
Apple Machine Learning Research
G
GRAHAM CLULEY
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
博客园 - 叶小钗
Last Week in AI
Last Week in AI
C
CERT Recently Published Vulnerability Notes
The Hacker News
The Hacker News
Jina AI
Jina AI
T
Tor Project blog
V
Vulnerabilities – Threatpost
酷 壳 – CoolShell
酷 壳 – CoolShell
Spread Privacy
Spread Privacy
博客园_首页
C
Cybersecurity and Infrastructure Security Agency CISA
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Simon Willison's Weblog
Simon Willison's Weblog
Security Latest
Security Latest
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
博客园 - 司徒正美
V2EX - 技术
V2EX - 技术
I
Intezer
The Cloudflare Blog
Cisco Talos Blog
Cisco Talos Blog
SecWiki News
SecWiki News
博客园 - 【当耐特】
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
L
Lohrmann on Cybersecurity
Scott Helme
Scott Helme
Google Online Security Blog
Google Online Security Blog
量子位
The Last Watchdog
The Last Watchdog
AI
AI
Application and Cybersecurity Blog
Application and Cybersecurity Blog
S
Security Affairs
P
Palo Alto Networks Blog
S
Secure Thoughts
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Attack and Defense Labs
Attack and Defense Labs

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
Building Claude Code from Scratch: A Minimal Agent in 393 Lines of C++
Tyler Tan · 2026-05-20 · via DEV Community

An AI coding assistant that reads your files, writes code, and runs shell commands. The core logic? A single while loop. I thought it was bullshit too, until I built one myself.

The project is called MoonieCode, and the code lives here: https://github.com/Tenaryo/MoonieCode. Written in C++23, clocking in at 393 lines of source (637 if you count tests). Here's what it looks like in action:

$ ./moonie-code -p "list all .cpp files in the project"

Enter fullscreen mode Exit fullscreen mode

A few seconds later Claude spits back your file list. What just happened? You gave it a sentence, it threw that sentence into an HTTP request, shipped it off to a Claude Haiku model somewhere in the cloud, Claude decided it needed to run find, MoonieCode ran it for Claude, fed the output back, and Claude formatted it into something human-readable.

That first step wasn't running bash. First it had to talk to the LLM. So let's start there: how do you get C++ and Claude to shake hands?

Shaking Hands with Claude

Talking to an LLM boils down to two moves: you HTTP POST a blob of JSON at it, and it sends a blob of JSON back. MoonieCode's HttpClient is a 25-line class whose guts are basically this:

cpr::Response response = cpr::Post(
    cpr::Url{base_url_ + "/chat/completions"},
    cpr::Header{{"Authorization", "Bearer " + api_key_},
                {"Content-Type", "application/json"}},
    cpr::Body{request_body.dump()}
);

Enter fullscreen mode Exit fullscreen mode

cpr is a C++ wrapper around libcurl that handles the HTTP plumbing so you don't have to. You stuff your API key into the Authorization header, pack your JSON into the body, and POST to OpenRouter, an LLM API gateway that forwards the request to Claude for you.

So what's in that JSON? Two things: messages and tools.

messages is an array holding the conversation history between you and Claude. At the start it's just one entry:

{"role": "user", "content": "list all .cpp files in the project"}

Enter fullscreen mode Exit fullscreen mode

tools is another array that tells Claude "here's what you have at your disposal." Each tool is a JSON object with a name, a description, and a parameter schema. Claude scans the list and goes, alright, I can ask this program to read files, write files, and run commands for me.

After you fire off the request, Claude sends back a JSON response. And here's where it gets fun: Claude's response comes in exactly two flavors.

Flavor one, straight text. You ask "what's 1+1" and it just answers:

{"choices": [{"message": {"content": "1+1 equals 2"}}]}

Enter fullscreen mode Exit fullscreen mode

Flavor two, tool call. You ask it to "list all cpp files" and it can't answer directly, so it asks for help:

{"choices": [{"message": {"tool_calls": [{
  "id": "call_abc123",
  "function": {
    "name": "Bash",
    "arguments": "{\"command\": \"find . -name '*.cpp'\"}"
  }
}]}}]}

Enter fullscreen mode Exit fullscreen mode

It's saying "I can't do this myself, but run this command for me and I'll take it from there." Notice arguments is a string containing more JSON, Claude packed a shell command inside it.

Now the hard part: how does your code tell these two cases apart? If Claude gives you text, print it. If it wants a tool run, execute the tool. You need those two paths separated cleanly.

MoonieCode solves this with a very C++ move:

using ParsedResponse = std::variant<ContentResult, std::vector<ToolCall>>;

Enter fullscreen mode Exit fullscreen mode

std::variant works like a paranoid envelope: it contains either a letter (ContentResult) or a toolbox (a list of ToolCall objects), never both, never neither. And the compiler makes sure you handle both cases. Omit one, and your build fails.

Handling the variant means pairing it with std::visit and a classic C++ pattern called overloaded:

template <class... Ts>
struct overloaded : Ts... { using Ts::operator()...; };

Enter fullscreen mode Exit fullscreen mode

Six lines of template code that let you dispatch elegantly with lambdas:

std::visit(overloaded{
    [&](const ContentResult& r) { /* Claude answered, print it */ },
    [&](const std::vector<ToolCall>& tcs) { /* Claude wants tools, run them */ },
}, parsed);

Enter fullscreen mode Exit fullscreen mode

The beauty of this pattern is type safety. You physically cannot write code that forgets to handle one of the two possibilities. The compiler will chase you down until every branch exists. People love to complain that C++ is verbose, but this flavor of compile-time guardrail is genuinely satisfying when you're building something that has to not crash.

Alright, your program now knows what Claude wants. Next question: if Claude asked for a tool, what happens?

The While Loop Is the Soul of the Agent

Here's the entire agent loop in pseudocode:

push the user's prompt into messages
while (not done) {
    pack messages + tools into JSON
    POST to Claude
    parse Claude's response
    if (response is text) {
        print it, we're done
    } else if (response is tool calls) {
        append Claude's tool call records to messages
        for (each tool call) {
            execute it locally
            append the result to messages
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

That's it. No black magic, no secret sauce. Peel back the marketing and you find a while loop wrapping a four-step cycle: ask the LLM, see what it wants, if it answered you're done, if it asked for a tool you run it and ask again.

One detail that's easy to overlook: that messages array keeps growing. The "conversation history" with Claude isn't wiped between rounds, it just piles up layer by layer:

  1. Starts with one role: "user" message
  2. Claude says "run this command," so you append an role: "assistant" message with tool_calls
  3. Command finishes, you append a role: "tool" message with the output
  4. Next request carries the entire history, so Claude sees "last time I told you to run this, the result was this, now I will..."

That's the agent's "memory." No vector database, no fancy RAG pipeline, just push_back on a JSON array. Claude reads the full history and naturally chains multi-step reasoning.

What about stopping? MoonieCode has maxIterations = 30. If Claude chains 30 tool calls without giving a final answer, the program pulls the plug. It's a safety fuse that keeps the agent from spinning its wheels forever.

Of course, the real Claude Code is a different beast. Public information suggests its repo weighs in at over half a million lines of TypeScript. It doesn't use a crude 30-iteration cap, it runs a dynamic token budget system. It dispatches sub-agents to handle different tasks in parallel. It asks for confirmation before doing anything dangerous. It supports checkpointing so you can roll back when things explode. It speaks MCP to plug into external data sources. MoonieCode is roughly three orders of magnitude away from the real thing.

And yet. No matter how many layers of engineering get piled on top, the skeleton underneath is the same loop: ask the LLM, check what it wants, execute on its behalf, feed the result back in. That's what MoonieCode strips bare and shows you.

Doing Claude's Dirty Work

Claude says "I want to run find." That intent arrives as a JSON blob. Who turns it into an actual system call? ToolExecutor.

MoonieCode gives Claude three weapons: Read, Write, and Bash. When a tool call comes in, ToolExecutor::execute checks the name field and routes it:

auto ToolExecutor::execute(const ToolCall& tool_call) -> std::string {
    if (tool_call.name == "Read")  return handle_read(tool_call.arguments);
    if (tool_call.name == "Write") return handle_write(tool_call.arguments);
    if (tool_call.name == "Bash")  return handle_bash(tool_call.arguments);
    throw std::runtime_error("Unknown tool: " + tool_call.name);
}

Enter fullscreen mode Exit fullscreen mode

That's it. A plain if-else chain mapping an LLM's "intent" to local C++ functions. No reflection. No plugin registry. No factory pattern. A 393-line project doesn't need design patterns.

Of the three tools, Bash is the star because it hands Claude the nuclear launch codes, it can run literally any command. Read and Write could technically be emulated with Bash (read with cat, write with tee), but they got their own tools because file I/O is so frequent it'd be wasteful, and error-prone, to channel it all through a shell.

Here's what's inside Bash:

auto ToolExecutor::handle_bash(const nlohmann::json& arguments) -> std::string {
    const auto command = arguments["command"].get<std::string>();
    const auto full_cmd = command + " 2>&1";  // capture stderr too

    FILE* pipe = popen(full_cmd.c_str(), "r");
    std::string output;
    std::array<char, 4096> buffer{};
    std::size_t bytes_read = 0;
    while ((bytes_read = fread(buffer.data(), 1, buffer.size(), pipe)) > 0) {
        output.append(buffer.data(), bytes_read);
    }

    int status = pclose(pipe);
    int exit_code = WIFEXITED(status) ? WEXITSTATUS(status) : status;
    output += "\n[exit code: " + std::to_string(exit_code) + "]";
    return output;
}

Enter fullscreen mode Exit fullscreen mode

Pull the command field out of the JSON, tack on 2>&1 to swallow stderr too, popen it, loop fread until the pipe runs dry, pclose to clean up and grab the exit code, then mash stdout, stderr, and exit code into one string and toss it back.

Where does that string go? Right back into the messages array, wearing the role: "tool" badge. Next time Claude gets a request, it reads that message and knows exactly what happened when the command ran. Loop this, and Claude starts to feel like a pilot in a cockpit: the dashboard (messages) shows current state, the joystick (tools) lets it take action.

Read and Write follow the exact same formula: yank parameters from JSON, do local I/O, return a result string. Read uses ifstream to slurp files whole. Write uses ofstream and auto-creates parent directories with create_directories. So clean there's not much else to say.

What 393 Lines Actually Mean

The real Claude Code is reportedly over half a million lines of TypeScript. It has sub-agent dispatching, permission gatekeeping, checkpoint rollback, MCP multi-protocol adaptation, multi-model routing, context window compression, and a long list of features you won't find anywhere in MoonieCode. In terms of capabilities, MoonieCode isn't even a rounding error.

But here's the counterintuitive part: no matter how much engineering gets layered on, the agent loop at the center is the same one. Ask the LLM, receive tool calls, execute locally, feed results back. Those four steps are the Newton's laws of this space. Everything else is engineering.

MoonieCode's 393 lines don't have the right to be compared to Claude Code on features. But they do one thing well: they strip the agent skeleton down to the bone, rip off every layer of engineering skin, and let you stare directly at the heartbeat of an AI coding assistant. Once you've internalized those 393 lines, every AI coding tool you encounter will auto-decompile in your head into "okay, the permissions system is on top, sub-agent scheduling underneath, and at the very bottom... still a while loop."