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

推荐订阅源

S
Security Affairs
S
Schneier on Security
T
Tenable Blog
G
GRAHAM CLULEY
Latest news
Latest news
D
Darknet – Hacking Tools, Hacker News & Cyber Security
A
Arctic Wolf
I
Intezer
Cyberwarzone
Cyberwarzone
T
The Exploit Database - CXSecurity.com
T
Tailwind CSS Blog
K
Kaspersky official blog
Blog — PlanetScale
Blog — PlanetScale
C
Cyber Attacks, Cyber Crime and Cyber Security
T
Threat Research - Cisco Blogs
爱范儿
爱范儿
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
博客园 - 叶小钗
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Recent Commits to openclaw:main
Recent Commits to openclaw:main
P
Palo Alto Networks Blog
WordPress大学
WordPress大学
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
博客园 - 司徒正美
The Cloudflare Blog
Help Net Security
Help Net Security
罗磊的独立博客
博客园 - 聂微东
Jina AI
Jina AI
Project Zero
Project Zero
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
L
LINUX DO - 最新话题
V
V2EX
人人都是产品经理
人人都是产品经理
美团技术团队
博客园 - 【当耐特】
Spread Privacy
Spread Privacy
J
Java Code Geeks
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Security Latest
Security Latest
The Last Watchdog
The Last Watchdog
Stack Overflow Blog
Stack Overflow Blog
雷峰网
雷峰网
S
Securelist
Forbes - Security
Forbes - Security
博客园 - 三生石上(FineUI控件)
Microsoft Azure Blog
Microsoft Azure Blog
P
Privacy International News Feed
宝玉的分享
宝玉的分享
C
CERT Recently Published Vulnerability Notes

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 a Safe, Local AI Coding Agent with Node.js
Gaurav Kumar Singh · 2026-06-19 · via DEV Community

Welcome to the 4th article of the MCP and RAG with JS series.

In this article, we will learn what AI agents are by building a practical, beginner-friendly coding agent in JavaScript. We will use a locally running LLM, Mistral on Ollama.

You do not need any paid subscription or API key. Everything runs locally on your machine, so this is accessible for learning, testing, and experimenting.

What We Are Building

We are building a local personal coding agent.

It runs in the terminal and helps us understand a JavaScript project. It can:

  • list project files
  • read project files
  • search text
  • explain code
  • find possible bugs
  • propose code changes

The important safety rule is this:

The agent can inspect files, but it does not directly edit them. If it wants to change something, it only returns a patch proposal for a human developer to review.

So in simple words, we are building a small local coding assistant.

Why This Helps Us Learn

AI agents can sound complicated, but most agent systems use a few common patterns.

In this project, we will learn those patterns with plain JavaScript:

  • Agent loop: repeat until the model gives a final answer.
  • Tool calling: let the model request specific JavaScript functions.
  • Tool allowlist: only allow approved tools to run.
  • System prompt: tell the model how it should behave.
  • JSON action protocol: make the model respond with structured JSON.
  • Model adapter: keep the Ollama HTTP code in one small file.
  • Safety boundary: keep file access inside the project root.
  • Human-in-the-loop changes: propose patches instead of applying them.
  • Tests for safety: verify path traversal, large file, and missing file behavior.

These same ideas appear in larger AI-agent frameworks. This project keeps them small enough to understand.

The Big Idea

A normal chatbot usually works like this:

User asks question -> Model answers

An agent works more like this:

User asks question
  -> Model decides what to do
  -> JavaScript runs a safe tool
  -> Model sees the tool result
  -> Model answers

The model does not directly read your files or run commands. It asks for a tool, and your JavaScript code decides whether that tool is allowed.

That is the main idea behind this project.

Project Structure

You can explore and clone the complete codebase in the coding-agents GitHub repository.
The important files are:

.
|-- package.json
|-- src
|   |-- cli.js
|   |-- agent.js
|   |-- ollama.js
|   `-- tools.js
`-- test
    `-- tools.test.js

Each file has a clear job:

  • src/cli.js: terminal entry point
  • src/agent.js: agent loop and tool dispatch
  • src/ollama.js: local Ollama API client
  • src/tools.js: safe filesystem tools
  • test/tools.test.js: safety and tool behavior tests

1: CLI Entry Point

The app starts in src/cli.js.

It imports the agent:

import { runAgent } from "./agent.js";

Then it chooses the project root and model:

const root = options.root || process.cwd();
const model = options.model || process.env.OLLAMA_MODEL || "mistral";

This means:

  • use --root if the user provides it
  • otherwise use the current folder
  • use --model if provided
  • otherwise use OLLAMA_MODEL
  • otherwise use mistral

The CLI supports two modes.

One-shot mode:

npm start -- "Explain src/tools.js"

Interactive mode:

npm start

In both cases, the CLI eventually calls:

const answer = await runAgent({ goal, root, model, verbose });

So the CLI is only responsible for input and output. The real agent behavior lives in runAgent.

2: Agent Loop

The main function is in src/agent.js:

export async function runAgent({ goal, root, model, verbose = false }) {

At the start, it creates the available tools:

const { createTools } = await import("./tools.js");
const tools = createTools({ root });

Passing root is important. It tells the tools which folder they are allowed to inspect.

Then the agent creates a message history:

const messages = [
  {
    role: "system",
    content: buildSystemPrompt(tools)
  },
  {
    role: "user",
    content: goal
  }
];

The system message contains rules for the model. The user message contains the developer's request.

Then the agent runs a loop:

for (let step = 1; step <= MAX_STEPS; step += 1) {
  const prompt = renderPrompt(messages);
  const raw = await generateWithOllama({ prompt, model });
  const action = parseAction(raw);

  // final answer or tool call
}

MAX_STEPS is set to 8, so the agent cannot loop forever.

This loop is the heart of the agent:

messages -> prompt -> model -> action -> tool or final answer

3: System Prompt

The system prompt tells the model how to behave.

In this project, the prompt says the model should:

  • inspect files before making project-specific claims
  • never claim a patch was applied
  • use propose_patch only for suggested changes
  • return exactly one JSON object

It also describes the available tools:

const toolDescriptions = Object.entries(tools)
  .map(([name, tool]) => `- ${name}: ${tool.description} Parameters: ${JSON.stringify(tool.parameters)}`)
  .join("\n");

This lets the model know what it can ask for.

The model must respond in one of two shapes.

To call a tool:

{"type":"tool","name":"read_file","arguments":{"path":"src/example.js"}}

To finish:

{"type":"final","answer":"Your answer here."}

This is a simple JSON action protocol. It is easy to understand because there is no hidden framework magic.

4: Local Model Adapter

The file src/ollama.js keeps the Ollama API call separate from the rest of the app.

The default local URL is:

const DEFAULT_OLLAMA_URL = "http://127.0.0.1:11434";

The function sends the prompt to Ollama:

const response = await fetch(`${baseUrl}/api/generate`, {
  method: "POST",
  headers: {
    "content-type": "application/json"
  },
  body: JSON.stringify({
    model,
    prompt,
    stream: false,
    options: {
      temperature
    }
  })
});

Then it returns the model text:

const data = await response.json();
return data.response || "";

This file has one job:

prompt in -> Ollama HTTP request -> model response out

Keeping this in one file makes it easier to swap models later.

5: Tool Calling

After Ollama responds, the agent parses the response:

const action = parseAction(raw);

If the model gives a final answer, the agent returns it:

if (action.type === "final") {
  return action.answer;
}

If the model asks for a tool, the agent checks whether that tool exists:

const tool = tools[action.name];
if (!tool) {
  messages.push({
    role: "tool",
    content: JSON.stringify({
      error: `Unknown tool: ${action.name}`,
      allowedTools: Object.keys(tools)
    })
  });
  continue;
}

This is very important.

The model cannot invent tools. It can only use tools that exist in the local JavaScript object.

Then the agent runs the tool:

const result = await tool.run(action.arguments || {});

And sends the result back into the message history:

messages.push({
  role: "tool",
  content: JSON.stringify({
    tool: action.name,
    result
  })
});

Now the model can use real project information instead of guessing.

Example flow:

User: Explain src/tools.js
Model: calls read_file
JavaScript: reads the file safely
Model: sees the file content
Model: gives final explanation

6: Safe Tools

The tools live in src/tools.js.

The project has four tools:

list_files
read_file
search_text
propose_patch

Each tool has:

  • description: tells the model what it does
  • parameters: tells the model what arguments it accepts
  • run: the actual JavaScript function

Example shape:

read_file: {
  description: "Read a UTF-8 text file from inside the project root.",
  parameters: {
    path: "File path relative to project root."
  },
  run: async ({ path: filePath } = {}) => {
    // safe implementation
  }
}

The tools are intentionally narrow.

list_files lists files under the project root.

read_file reads one text file if it is safe and not too large.

search_text searches project files for a string or regex.

propose_patch returns a patch proposal, but does not apply it.

That last point matters. The model can suggest changes, but a human still reviews them.
** use a safe regex engine if you are planning to expose the agent to external user inputs. **

7: Path Safety With safeResolve

The most important safety function is:

export function safeResolve(root, requestedPath) {
  const absolute = path.resolve(root, requestedPath);
  const relative = path.relative(root, absolute);

  if (relative.startsWith("..") || path.isAbsolute(relative)) {
    throw new Error(`Path escapes project root: ${requestedPath}`);
  }

  return absolute;
}

This blocks path traversal.

For example, this should be allowed:

src/tools.js

But this should be blocked:

../outside.txt

Why?

Because the agent should only inspect the selected project folder. Model output is not trusted input, so every requested path goes through safeResolve.

This is one of the most important lessons in agent development:

Give the model useful tools, but put real safety checks in code.

8: Size Limits

The tool layer also avoids reading huge files:

const MAX_READ_BYTES = 80_000;
const MAX_SEARCH_FILE_BYTES = 250_000;

read_file throws an error if a file is too large.

search_text skips files that are too large.

This protects the model context and keeps the agent responsive.

9: Patch Proposals, Not Auto-Edits

The propose_patch tool returns:

{
  summary,
  patch,
  applied: false,
  note: "Patch proposal only. Review before applying."
}

This is a human-in-the-loop design.

The agent can help you think and propose changes, but it does not silently modify your files.

For a beginner agent, this is a good safety tradeoff.

10: Tests for Safety

The project uses Vitest.

From package.json:

{
  "scripts": {
    "test": "vitest run",
    "test:watch": "vitest"
  }
}

The tests cover the dangerous parts:

  • safeResolve allows normal paths
  • safeResolve blocks ../ traversal
  • list_files handles missing directories
  • read_file rejects missing paths and directories
  • read_file rejects large files
  • search_text skips large files
  • propose_patch returns applied: false

Example traversal test:

expect(() => safeResolve(fixtureProjectRoot, "../outside.txt")).toThrow(/escapes project root/);

Example large-file test:

await expect(tools.read_file.run({ path: "large-read.txt" })).rejects.toThrow(/too large to read safely/);

Tests are not just for correctness here. They protect the safety boundary of the agent.

Complete Request Flow

If you run:

npm start -- "Explain src/tools.js"

the flow is:

  1. src/cli.js receives the request.
  2. It calls runAgent.
  3. src/agent.js creates safe tools.
  4. The system prompt describes the rules and tools.
  5. src/ollama.js sends the prompt to local Ollama.
  6. The model returns a JSON action.
  7. If it asks for a tool, the agent checks the allowlist.
  8. The tool runs with safety checks.
  9. The tool result goes back to the model.
  10. The model returns a final answer.

That is an AI agent in practical terms.

It is an LLM connected to a controlled loop, safe tools, and clear rules.

How to Run It

Requirements:

  • Node.js 18 or newer
  • Ollama installed
  • Mistral pulled locally

Pull the model:

ollama pull mistral

Start Ollama:

ollama serve

Run the agent:

npm start

Ask one question:

npm start -- "Explain src/agent.js"

Inspect another project:

npm start -- --root /path/to/project "Find bugs in the main CLI file"

Run tests:

npm test

Run syntax checks:

npm run check

What to Remember

An AI agent is not just an LLM.

An AI agent is usually:

LLM + loop + tools + context + safety rules

In this project:

  • the CLI gets the user request
  • the agent loop manages steps
  • Ollama provides the local model
  • tools provide controlled abilities
  • safeResolve protects file access
  • tests protect the safety behavior

The model can request actions, but JavaScript decides what actually runs.

That is the key idea.

Final Thoughts

This project is intentionally small, but it teaches the foundation behind many larger agent systems.

Once you understand this version, you can explore more advanced ideas like:

  • memory across chat turns
  • streaming model output
  • richer tool schemas
  • patch validation
  • confirmation-based patch applying
  • MCP tools
  • RAG over larger codebases

But the core idea stays the same:

Mistral is a good simple default for learning, but when you are building stronger coding agents, coding-focused models usually give better results. Good options to try are qwen2.5-coder, deepseek-coder, and Gemma.

Build useful tools, keep them narrow, and let your application code enforce the safety boundaries.