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

推荐订阅源

博客园_首页
C
Cybersecurity and Infrastructure Security Agency CISA
C
Cyber Attacks, Cyber Crime and Cyber Security
Project Zero
Project Zero
P
Proofpoint News Feed
D
Darknet – Hacking Tools, Hacker News & Cyber Security
C
Cisco Blogs
V
Vulnerabilities – Threatpost
G
GRAHAM CLULEY
N
News | PayPal Newsroom
NISL@THU
NISL@THU
雷峰网
雷峰网
J
Java Code Geeks
Latest news
Latest news
aimingoo的专栏
aimingoo的专栏
Microsoft Azure Blog
Microsoft Azure Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Cisco Talos Blog
Cisco Talos Blog
Hacker News: Ask HN
Hacker News: Ask HN
AWS News Blog
AWS News Blog
Application and Cybersecurity Blog
Application and Cybersecurity Blog
T
The Exploit Database - CXSecurity.com
P
Privacy International News Feed
C
CXSECURITY Database RSS Feed - CXSecurity.com
Vercel News
Vercel News
Spread Privacy
Spread Privacy
V2EX - 技术
V2EX - 技术
S
Schneier on Security
K
Kaspersky official blog
Recent Announcements
Recent Announcements
T
Threat Research - Cisco Blogs
B
Blog RSS Feed
S
SegmentFault 最新的问题
Security Archives - TechRepublic
Security Archives - TechRepublic
Stack Overflow Blog
Stack Overflow Blog
Hugging Face - Blog
Hugging Face - Blog
Apple Machine Learning Research
Apple Machine Learning Research
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
W
WeLiveSecurity
PCI Perspectives
PCI Perspectives
The GitHub Blog
The GitHub Blog
The Last Watchdog
The Last Watchdog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
博客园 - 【当耐特】
Engineering at Meta
Engineering at Meta
Scott Helme
Scott Helme
Recent Commits to openclaw:main
Recent Commits to openclaw:main
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
量子位
A
Arctic Wolf

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
What I learned building an AI agent loop in Go
Lucas Neves · 2026-05-18 · via DEV Community

Hello there!

A few months ago I built nevinho, a personal AI agent I run on my own machine. Bash, file edits, web search, voice input, the works. It taught me a lot, but the whole thing was hardcoded around my own use case. Anyone who wanted something similar had to fork it and rip it apart.

So I started over. Vikusha is the same idea, but as a Go framework. You bring your own system prompt, your own tools, your own transports, and the harness handles the rest.

Which means I'm writing the core agent loop again. This time as a reusable framework, so others can build their own agents on top of it instead of forking mine.

This post is about that loop. The thing every AI coding tool, every chatbot with tools, every "AI agent" is doing under the hood. Once you see it, you can't unsee it.

What an agent actually does

When you ask an AI assistant "what's in this directory?", a lot looks like it's happening. The model "decides" to run a command, "reads" the output, "answers" you. It feels intelligent.

What's really happening is a loop. You send the model your message plus a list of tools it can call. The model replies with either text (it's answering you) or a tool call (it wants to run something). If it called a tool, you run it, send the result back, and ask again. Eventually it replies with text and you're done.

That's it. That's the agent.

loop:
  response = provider.complete(system, messages, tools)
  text, tool_calls = split(response.content)
  if no tool_calls: return text
  messages += assistant(response.content)
  messages += user(run each tool → tool_result)
end
cap iterations

Enter fullscreen mode Exit fullscreen mode

No "reasoning engine", no chain-of-thought magic. The model decides what to do, you execute, the model sees the output, the model decides again. The loop is the abstraction.

The four things that bit me

When I first wrote this I got it wrong in roughly four ways. Each one took me a confusing afternoon to figure out.

Exit on absence of tool_use, not on stop_reason. Anthropic's API returns a stop_reason field. It feels like the right exit condition. It isn't. stop_reason can be max_tokens while there are still tool calls in the response. The actual signal is whether the response content has any tool_use blocks. If yes, run them and loop. If no, you're done.

Send the assistant's full content back, unchanged. When the model returns text plus tool calls, you have to append both as one message in the conversation history. If you split them, the API rejects the next request because the tool result references a tool_use id that isn't in the previous message anymore.

All tool results go in one user message. If the model called three tools in parallel, all three results have to come back in a single user message containing three tool_result blocks. Putting them in three separate messages breaks the pairing.

Errors are data, not exceptions. If a tool crashes or returns garbage, don't abort the loop. Wrap the error in a tool_result with is_error: true and send it back to the model. The model sees the failure and either retries with different input or tells the user what happened. If you throw, the user gets nothing.

These four rules are the entire correctness of the loop. Everything else is wrapping.

Two providers, same loop

Here's where it gets interesting. Anthropic and OpenAI both support tool calling, but their wire formats are nothing alike.

Anthropic puts tool calls inside the assistant's content array, alongside text blocks. OpenAI puts them in a separate tool_calls field on the message. Anthropic sends tool arguments as a JSON object. OpenAI sends them as a JSON-encoded string. Anthropic puts the system prompt at the top level of the request. OpenAI prepends it as a message with role "system".

If you build the loop against one of them, the other looks like a totally different problem.

The fix is to have your own internal representation and translate at the edges. In Vikusha I have a generic llm.Block type with three variants: text, tool_use, tool_result. The agent loop only knows about blocks. Each provider has a Complete method that takes a generic request and returns a generic response. The translation lives inside the provider package, hidden behind the interface.

type Provider interface {
    Name() string
    Complete(ctx context.Context, req *Request) (*Response, error)
}

Enter fullscreen mode Exit fullscreen mode

That's the whole contract. Plug in Anthropic, OpenAI, OpenRouter, Ollama, whatever. The loop doesn't care.

This abstraction earns its keep the moment you switch providers. I started building against Anthropic, ran out of API credit, switched to OpenRouter (which speaks the OpenAI dialect), and the agent code didn't change a line. Same loop, same Chat call, same tool execution. Just a different constructor.

Tools, the easy part

A tool in Vikusha is anything that satisfies this interface:

type Tool interface {
    Name() string
    Description() string
    Schema() json.RawMessage
    Run(ctx context.Context, input json.RawMessage) (string, error)
}

Enter fullscreen mode Exit fullscreen mode

Name and description are what the model sees when deciding whether to call you. Schema is JSON schema for the input parameters. Run executes the thing and returns text.

The first real tool I built was file_read. It's about 30 lines.

func (r *Read) Name() string { return "file_read" }

func (r *Read) Description() string {
    return "Read the contents of a file at the given path."
}

func (r *Read) Schema() json.RawMessage {
    return json.RawMessage(`{
      "type": "object",
      "properties": {"path": {"type": "string"}},
      "required": ["path"]
    }`)
}

func (r *Read) Run(ctx context.Context, input json.RawMessage) (string, error) {
    var in struct{ Path string }
    if err := json.Unmarshal(input, &in); err != nil {
        return "", err
    }
    data, err := os.ReadFile(in.Path)
    if err != nil {
        return "", err
    }
    return string(data), nil
}

Enter fullscreen mode Exit fullscreen mode

The model gets the name, description, and schema in the request. When it wants to read a file, it sends back {"name": "file_read", "input": {"path": "go.mod"}}. The loop looks the tool up by name, runs it, and feeds the result back as a tool_result block.

Same shape for any tool. Bash, web fetch, Notion, whatever.

The smallest working agent

Putting it all together looks like this:

reg := tool.NewRegistry()
reg.Register(file.NewRead())

a, err := agent.New(agent.Options{
    Name:         "reader",
    Model:        "openai/gpt-4o-mini",
    SystemPrompt: "You answer questions about files. Use file_read.",
    Provider:     llm.NewOpenRouter(apiKey),
    Tools:        reg,
})

reply, _ := a.Chat(ctx, "lucas", "Read go.mod and tell me the module name.")
fmt.Println(reply)

Enter fullscreen mode Exit fullscreen mode

That's a working agent. The model gets the question, sees it has a file_read tool, calls it with path: "go.mod", the loop reads the file, feeds the contents back, the model extracts the module name and answers in plain text.

No frameworks, no abstractions on top of abstractions. One interface per concept, one loop, one provider call per round.

What's next

Right now I have a single-turn agent that can read files. The obvious next step is bash. Every coding agent needs to run commands, and that's the difference between an agent that can look at things and an agent that can actually do them.

The interesting part about bash isn't the implementation. It's the safety wrap. A tool that runs arbitrary shell commands needs a timeout, an output cap, and some way to catch dangerous operations before they execute. That's where most of the design work goes, and it's the next thing I want to write about.

The loop itself won't change. Same Tool interface, same Schema and Run pattern as file_read. Which is the point. The loop is the load-bearing part of the harness. Tools are just things you plug in.

The code is on GitHub, MIT licensed. There are two runnable examples in examples/ if you want to try it. The whole core is under 500 lines so far, including both providers. Feel free to open an issue or read along as it grows.

Hope this was useful!