Skip to content
← All posts

Agentic AI in JavaScript: Build a Node.js Agent and Deploy It

What makes an AI system agentic, and how to build one in Node.js: an agent loop with tool calling, a task scheduler the agent can use, guardrails, and cloud deployment.

Implementing Agentic AI Solutions with JavaScript and Cloud Infrastructure

This guide shows how to build an agentic AI feature in JavaScript: what separates an agent from an ordinary chatbot, a working Node.js agent loop that calls your own functions as tools, the guardrails to add before real users touch it, and how to run it on AWS or Google Cloud.

What "agentic" means in practice

Agentic AI describes systems that decide for themselves which steps to take towards a goal, within limits you set. In most applications built today, that means four parts:

  1. A language model that reads the request and decides what to do next.
  2. Tools: functions in your code the model can ask to call, such as "look up an order" or "add a task".
  3. A loop: your code runs the tool the model asked for, sends back the result, and asks the model again, until it has an answer.
  4. Limits: a maximum number of steps, input validation and permissions, so the agent can't run away with your API budget or your data.

Compare that with a classic intent-based chatbot. It matches what the user typed to an intent you defined and returns the response you wrote. It never chooses an action you didn't script. That's fine for FAQs, but it isn't an agent. The two examples below show the difference.

Why JavaScript works for agents

Most of an agent's time is spent waiting: for the model's API, for a database, for another service. That's I/O-bound work, which Node.js handles well with async/await. The major model providers publish official JavaScript SDKs, and if your front end is already JavaScript, the whole feature stays in one language.

What JavaScript isn't good at is training large models. Agents don't need that: they call hosted models. If you want to run smaller models inside Node.js itself, such as a sentiment classifier, see our guide to machine learning features in Node.js.

Example 1: an intent-based chatbot (the non-agentic baseline)

This Express endpoint passes a message to a Dialogflow ES agent and returns the matching intent's response. Install the dependencies:

mkdir chatbot && cd chatbot
npm init -y
npm install express @google-cloud/dialogflow

Then create index.js:

const express = require('express');
const { SessionsClient } = require('@google-cloud/dialogflow');

const app = express();
app.use(express.json());

const projectId = process.env.DIALOGFLOW_PROJECT_ID;
const client = new SessionsClient(); // uses GOOGLE_APPLICATION_CREDENTIALS

app.post('/chat', async (req, res) => {
  const { sessionId, message } = req.body;
  const sessionPath = client.projectAgentSessionPath(projectId, sessionId);

  const [response] = await client.detectIntent({
    session: sessionPath,
    queryInput: { text: { text: message, languageCode: 'en' } },
  });

  res.json({ reply: response.queryResult.fulfillmentText });
});

app.listen(3000, () => console.log('Chatbot listening on port 3000'));

Test it with:

curl -X POST http://localhost:3000/chat \
  -H "Content-Type: application/json" \
  -d '{"sessionId": "user-123", "message": "What are your opening hours?"}'

Pass a different sessionId for each user, so Dialogflow keeps their conversations apart. Earlier versions of this example installed the old dialogflow package while importing @google-cloud/dialogflow, and used one fixed session ID for everyone; both are fixed above.

The bot only knows the intents you've built. Ask it something else and it falls back to a default reply.

Example 2: an agent that manages a task list

Here the model decides when to read the task list, when to add to it and what to tell the user. The scheduling logic is plain JavaScript: tasks sorted by priority, then by deadline. The agent uses it as a tool.

This example uses Anthropic's SDK (npm install @anthropic-ai/sdk). OpenAI's and Google's SDKs support tool calling with the same loop; only the field names differ. Set ANTHROPIC_API_KEY, and set ANTHROPIC_MODEL to a current model name from the provider's documentation.

// agent.mjs
import Anthropic from '@anthropic-ai/sdk';

const client = new Anthropic(); // reads ANTHROPIC_API_KEY
const MODEL = process.env.ANTHROPIC_MODEL;

const tasks = [
  { name: 'Write report', deadline: '2026-11-30', priority: 3 },
  { name: 'Prepare presentation', deadline: '2026-11-20', priority: 1 },
  { name: 'Email client', deadline: '2026-11-15', priority: 2 },
];

// Highest priority first, then earliest deadline.
function scheduleTasks(list) {
  return [...list].sort(
    (a, b) => b.priority - a.priority || new Date(a.deadline) - new Date(b.deadline)
  );
}

const tools = [
  {
    name: 'list_tasks',
    description:
      'Returns every task in schedule order: highest priority first, then earliest deadline. Call it before answering any question about what to do next.',
    input_schema: { type: 'object', properties: {} },
  },
  {
    name: 'add_task',
    description:
      'Adds one task to the list. Priority is 1 (low) to 3 (high). Deadline is a date in YYYY-MM-DD format.',
    input_schema: {
      type: 'object',
      properties: {
        name: { type: 'string' },
        deadline: { type: 'string', description: 'YYYY-MM-DD' },
        priority: { type: 'integer', minimum: 1, maximum: 3 },
      },
      required: ['name', 'deadline', 'priority'],
    },
  },
];

function runTool(name, input) {
  if (name === 'list_tasks') {
    return scheduleTasks(tasks);
  }
  if (name === 'add_task') {
    // Never trust tool input: validate it like any user input.
    const validDate = /^\d{4}-\d{2}-\d{2}$/.test(input.deadline);
    const validPriority = [1, 2, 3].includes(input.priority);
    if (!validDate || !validPriority || !input.name) {
      return { error: 'Invalid task. Check the name, date format and priority.' };
    }
    tasks.push({ name: input.name, deadline: input.deadline, priority: input.priority });
    return { ok: true, count: tasks.length };
  }
  return { error: `Unknown tool: ${name}` };
}

export async function runAgent(userMessage, maxSteps = 5) {
  const messages = [{ role: 'user', content: userMessage }];

  for (let step = 0; step < maxSteps; step++) {
    const response = await client.messages.create({
      model: MODEL,
      max_tokens: 1024,
      system: 'You manage a task list. Use the tools. Never invent tasks.',
      tools,
      messages,
    });

    messages.push({ role: 'assistant', content: response.content });

    // No tool requested: the model has its final answer.
    if (response.stop_reason !== 'tool_use') {
      return response.content
        .filter((block) => block.type === 'text')
        .map((block) => block.text)
        .join('\n');
    }

    // Run every tool the model asked for and send the results back.
    const results = response.content
      .filter((block) => block.type === 'tool_use')
      .map((block) => ({
        type: 'tool_result',
        tool_use_id: block.id,
        content: JSON.stringify(runTool(block.name, block.input)),
      }));

    messages.push({ role: 'user', content: results });
  }

  return 'Stopped after too many steps.';
}

console.log(
  await runAgent('Add "Book venue" due 2026-11-18 at priority 3, then tell me what to do first.')
);

Run it with node agent.mjs. For that request the model will typically call add_task, then list_tasks, then answer in plain language. You didn't script that order; the model chose it from the tool descriptions. That's the agentic part, and it's why the descriptions deserve as much care as the code.

Guardrails before real users

  • Cap the steps. The maxSteps limit above stops a confused agent looping forever and running up your bill.
  • Validate every tool input on the server, as add_task does. The model can produce a wrong date or an out-of-range value.
  • Start read-only. Give the agent tools that read data first. For anything that deletes, pays or emails, ask the user to confirm before the tool runs.
  • Treat outside text as data, not instructions. Web pages, emails and documents the agent reads can contain text written to hijack it (prompt injection). Don't give an agent that reads untrusted content powerful tools without a confirmation step.
  • Log every tool call with its input and result, so you can see why the agent did what it did.

When the same tools need to serve several agents or apps, look at the Model Context Protocol (MCP), an open standard for exposing tools and data to AI applications through a common interface instead of wiring each one up separately.

Deploying on cloud infrastructure

The example keeps tasks in memory, which disappears on restart and isn't shared between server instances. For production:

  • Compute. Short request-and-response agents fit serverless platforms such as AWS Lambda or Google Cloud Run. Long-running agents belong in containers (Cloud Run, or ECS with Fargate on AWS), since a Lambda function can run for at most 15 minutes.
  • State. Store conversations and task data in a database such as DynamoDB, Firestore or PostgreSQL, keyed by user.
  • Secrets. Keep API keys in AWS Secrets Manager or Google Secret Manager, never in code or the repository.
  • Long jobs. Put slow work on a queue (Amazon SQS or Google Pub/Sub) and let a worker pick it up, so the user isn't left waiting on an open request.
  • Models inside your cloud account. Amazon Bedrock and Google's Vertex AI offer hosted models with billing and access control through your existing cloud account.
  • Monitoring. Track errors, latency, token usage and cost with Amazon CloudWatch or Google Cloud Observability. Alert on unusual token spend: it's often the first sign of a looping agent.
  • The basics still apply: deploy through a CI/CD pipeline, and encrypt data at rest and in transit.

If cloud terms are new to you, AWS has a plain-English overview of cloud infrastructure.

Where to go from here

Get the task-list agent working locally, then replace its two tools with two from your own application, starting with read-only ones. Most of the effort in a useful agent goes into well-described tools and the guardrails around them, not the loop. If your agent sits behind a React interface with a Python service doing the heavy lifting, see connecting a React frontend to a Python backend.

Written by

Wow Themes Team

The people behind Wow Themes

The Wow Themes team builds and supports WordPress themes and plugins, plus Vue and React templates. On the blog we write about building, running and securing websites: WordPress and WooCommerce, JavaScript, performance and the tools we use day to day.

ShareXLinkedInFacebook

Comments

No comments yet.

Comments are closed on this post.

Keep reading

Implementing AI-Driven Features in a JavaScript Environment: A Combination of Node.js and Machine Learning

· 6 min read

Machine Learning in Node.js: Libraries and Working Examples

How to add machine learning features to a Node.js app: which approach fits, which libraries are still maintained, and working code for regression, sentiment analysis and recommendations.

Optimizing MCP Servers for High-Performance Computing Featured image

· 8 min read

HPC Performance Tuning on a Managed Computing Platform

What you can tune on a managed HPC platform and what your provider controls: Slurm job settings, CPU and memory placement, parallel storage, networking, profiling and scaling.

Implementing Machine Learning Models with Python on MCP Servers

· 6 min read

Implementing Machine Learning Models with Python on MCP Servers

The rapid advancements in machine learning (ML) and artificial intelligence (AI) have catalyzed numerous businesses to integrate these technologies into their infrastructure. MCP (Managed Cloud Platform) servers offer a robust environment for deploying machine learning models…