🕵️ SicherheitslückenWhat continuous operational resilience looks like under DORA(09.09.2026 um 17:53 Uhr)
🔧 AI Nachrichten OpenAI seeks tougher AI rules. CIOs may feel the ripple effects(10.09.2026 um 12:11 Uhr)
🔧 AI Nachrichten Mistral valued at €21bn after €3bn Series D funding round(08.09.2026 um 10:19 Uhr)
🪟 Windows TippsWindows XP's Cursor Indicator Is Getting a Windows 11 Refresh(25.08.2026 um 13:00 Uhr)
🕵️ SicherheitslückenWhat continuous operational resilience looks like under DORA(09.09.2026 um 17:53 Uhr)
🔧 AI Nachrichten OpenAI seeks tougher AI rules. CIOs may feel the ripple effects(10.09.2026 um 12:11 Uhr)
🔧 AI Nachrichten Mistral valued at €21bn after €3bn Series D funding round(08.09.2026 um 10:19 Uhr)
🪟 Windows TippsWindows XP's Cursor Indicator Is Getting a Windows 11 Refresh(25.08.2026 um 13:00 Uhr)

🔧 Programmierung 🕛 vor 2 Monaten 10 Min Lesezeit
0

Building a Safe, Local AI Coding Agent with Node.js

↗ Quelle (dev.to)
🗣️ Stimme:
📑 Inhaltsübersicht

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:




CODE
User asks question -> Model answers






An agent works more like this:




CODE
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:




CODE
.
|-- 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:




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






Then it chooses the project root and model:




CODE
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:




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






Interactive mode:




CODE
npm start






In both cases, the CLI eventually calls:




CODE
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:




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






At the start, it creates the available tools:




CODE
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:




CODE
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:




CODE
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:




CODE
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:




CODE
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:




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






To finish:




CODE
{"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:




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






The function sends the prompt to Ollama:




CODE
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:




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






This file has one job:




CODE
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:




CODE
const action = parseAction(raw);






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




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






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




CODE
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:




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






And sends the result back into the message history:




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






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



Example flow:




CODE
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:




CODE
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:




CODE
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:




CODE
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:




CODE
src/tools.js






But this should be blocked:




CODE
../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:




CODE
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:




CODE
{
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:




CODE
{
"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:




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






Example large-file test:




CODE
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:




CODE
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:




CODE
ollama pull mistral






Start Ollama:




CODE
ollama serve






Run the agent:




CODE
npm start






Ask one question:




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






Inspect another project:




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






Run tests:




CODE
npm test






Run syntax checks:




CODE
npm run check









What to Remember



An AI agent is not just an LLM.



An AI agent is usually:




CODE
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.

Vollständiger Original-Bericht
Ausführliche Details, Code-Beispiele & Hersteller-Stellungnahme auf dev.to.
↗ Original-Artikel auf dev.to lesen
Wie bewertest du diesen Beitrag?
1 Klick Feedback
Teilen mit Netzwerk & Team:

Community-Analysen & Experten-Meinungen 0

Verfasse deine eigene Analyse, teile Workarounds oder diskutiere diesen Vorfall im Blog.
Noch keine Community-Analyse verfasst. Markiere einen Textabschnitt oder klicke oben auf Eigene Analyse verfassen“!
Community Pulse: Relevanz-Einschätzung
1 Klick Experten-Votum
🔴 Akute Relevanz 0%
🟡 In Evaluierung 0%
🟢 Keine Auswirkung 0%
Spannende Innovation 0%
Verwandte Story-Cluster & Quellen (Vektor-KI)
Port 8095 Engine
1 Quelle
Sam Altman calls GPT-6 Astra rollout ‘messy’ as enterprise users wait for access
1 Quelle
Swiss government explores replacing Microsoft 365 with open-source software
1 Quelle
What continuous operational resilience looks like under DORA
Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten Building a Safe, Local AI Coding Agent with Node.js

Thematisch verwandte Begriffe: Building, Safe, Local, Coding · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...