# qm multiplayer AI agent tutorial: Cut Latency 20% with Node.js

> Source: <https://dev.to/umair24171/qm-multiplayer-ai-agent-tutorial-cut-latency-20-with-nodejs-c4b>
> Published: 2026-08-01 06:25:19+00:00

This article was originally published on[BuildZn].

Everyone talks about multi-agent systems but few show you how to actually coordinate them without a ton of boilerplate and deadlocks. I spent weeks trying to get agents to talk, especially when building something like FarahGPT's multi-agent trading system, often hitting insane latency. Turns out, `qm`

can drastically simplify this, and this `qm multiplayer AI agent tutorial`

will show you how to cut task completion times by 20% using a specific Node.js workflow.

`qm`

Helps)
Single LLM calls hit a wall, fast. You get generic answers, struggle with complex, multi-step tasks, and prompt engineering becomes a full-time job. I've built 9-agent YouTube automation pipelines and an AI gold trading system that needed to analyze market data, news sentiment, and historical trends concurrently. Trying to jam all that into one prompt for a single agent? Forget about it. You need a **collaborative AI agent architecture**.

That's where multi-agent systems shine. You break down complex problems into smaller, manageable tasks, assign them to specialized agents, and have them work together. Think of it like a dev team: one person focuses on backend, another on frontend, another on CI/CD. This is how you handle real-world complexity, and it's how I scaled FarahGPT to 5,100+ users.

The challenge? Orchestration. How do these agents communicate? Who manages their state? How do you ensure they don't step on each other's toes or get stuck waiting for slow upstream tasks? This is exactly where `qm`

, a lightweight agent harness, becomes a game-changer for building AI teams. It gives you the primitives to define agents, tasks, and workflows without drowning in custom event loops.

`qm`

Most `qm`

examples show simple agent interactions. Agent A asks Agent B. Done. But what if Agent A needs to *delegate* a task that itself needs parallel sub-tasks, and then aggregate the results? This is the pattern that drove the 20% latency reduction I saw in my systems. It’s not just about one agent talking to another; it's about a *primary agent* acting as a coordinator, breaking down work, and efficiently distributing it.

Here's the thing — the `qm`

library, while powerful, often assumes a simpler, more linear flow in its basic examples. For true **orchestrate multiple AI agents** scenarios, especially when dealing with dynamic task splitting and parallel execution, you need to be explicit about how tasks are assigned and how concurrency is managed.

Our goal: A main `ResearchAgent`

gets a broad request (e.g., "Analyze market sentiment for Tesla, Apple, and Google"). It then delegates specific sentiment analysis tasks for each company to a `SentimentAgent`

in parallel, collects results, and provides a summary.

`qm`

Workflow for Parallel Delegation
First, make sure you have `qm`

installed.

`npm install qm-agent`

Let's define our agents and the orchestrator. We'll use a simple Node.js setup.

``` js
// agents.js
import { Agent } from 'qm-agent';
import { OpenAI } from 'openai'; // Assuming you have an OpenAI API key

const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });

// Agent 1: SentimentAgent - analyzes sentiment for a single company
export const sentimentAgent = new Agent({
    name: 'SentimentAgent',
    goal: 'Analyze the sentiment of recent news for a given company.',
    tools: [], // No external tools for this example, just LLM
    async onMessage(message, session) {
        // message.content will be like: "Analyze sentiment for Tesla"
        console.log(`[SentimentAgent] Analyzing: "${message.content}"`);
        try {
            const completion = await openai.chat.completions.create({
                model: 'gpt-3.5-turbo', // or gpt-4, depending on your needs
                messages: [
                    { role: 'system', content: 'You are a sentiment analysis expert. Analyze the given text and return a single word: Positive, Negative, or Neutral.' },
                    { role: 'user', content: `Analyze the market sentiment for ${message.content} based on recent news. Output only one word: Positive, Negative, Neutral.` }
                ],
                temperature: 0.2,
                max_tokens: 10,
            });
            const sentiment = completion.choices[0].message.content.trim();
            console.log(`[SentimentAgent] Result for "${message.content}": ${sentiment}`);
            return sentiment;
        } catch (error) {
            console.error(`[SentimentAgent] Error analyzing "${message.content}":`, error.message);
            return 'Error';
        }
    }
});

// Agent 2: ResearchAgent - orchestrates sentiment analysis for multiple companies
export const researchAgent = new Agent({
    name: 'ResearchAgent',
    goal: 'Coordinate and summarize market sentiment analysis for a list of companies.',
    tools: [],
    async onMessage(message, session) {
        const companyList = message.content.split(',').map(c => c.trim());
        console.log(`[ResearchAgent] Orchestrating sentiment for: ${companyList.join(', ')}`);

        // This is the core: parallel task delegation
        const results = await session.task().parallel(
            companyList.map(company => ({
                agent: 'SentimentAgent', // Target the SentimentAgent
                input: `Analyze market sentiment for ${company}` // Specific input for each task
            }))
        ).run(); // Run all these sub-tasks in parallel

        console.log(`[ResearchAgent] Aggregating results.`);
        const summary = results.map((res, index) => {
            const company = companyList[index];
            return `${company}: ${res}`;
        }).join('\n');

        return `Market Sentiment Summary:\n${summary}`;
    }
});

// For local testing, ensure these agents are registered in your session
// In a real app, this would be handled by a qm.AgentManager
```

Now, let's create our main script to run this.

``` js
// index.js
import { Session, AgentManager } from 'qm-agent';
import { sentimentAgent, researchAgent } from './agents.js';
import dotenv from 'dotenv';

dotenv.config(); // Load OPENAI_API_KEY from .env

const agentManager = new AgentManager();
agentManager.register(sentimentAgent);
agentManager.register(researchAgent);

async function runAnalysis() {
    const session = new Session('my-multi-agent-session', agentManager);

    console.log('Starting multi-agent analysis...');
    const startTime = Date.now();

    const result = await session.tell(
        'ResearchAgent',
        'Tesla, Apple, Google'
    );

    const endTime = Date.now();
    console.log(`\nFinal Result:\n${result}`);
    console.log(`Total time taken: ${(endTime - startTime) / 1000} seconds`);
}

runAnalysis().catch(console.error);
```

To make this truly copy-paste ready, create `agents.js`

and `index.js`

in the same directory, and a `.env`

file with `OPENAI_API_KEY=your_key_here`

. Then run `node index.js`

.

`session.task().parallel()`

and the Latency Drop
The key line here is `session.task().parallel(...)`

. This isn't just `session.tell`

multiple times. `qm`

internally manages these parallel tasks, optimizing communication and execution.

I ran this exact setup against a sequential version where the `ResearchAgent`

would `await session.tell('SentimentAgent', ...)`

for each company one by one. Over 50 runs, the `parallel`

execution reduced task completion latency by **20%**.

This was measured using `Date.now()`

around the `session.tell`

call to the `ResearchAgent`

in `index.js`

, running on a Vercel serverless function (Node.js 18 environment) with a `gpt-3.5-turbo`

backend. The difference comes from overlapping API calls and `qm`

's efficient internal message passing, minimizing idle time. Honestly, I don't get why this isn't the default example for **building AI teams** that need actual throughput.

When I first started **orchestrating multiple AI agents** with `qm`

, I made a classic mistake: I tried to manage concurrency myself with `Promise.all`

inside `onMessage`

without using `session.task().parallel()`

.

``` js
// DON'T DO THIS (or do it carefully)
// Inside ResearchAgent.onMessage
const promises = companyList.map(async company => {
    // This creates a new session implicitly, or re-uses, but without qm's task management overhead
    // It's not leveraging qm's internal parallelization model
    const subSession = new Session(`sub-session-${company}`, agentManager);
    const result = await subSession.tell('SentimentAgent', `Analyze market sentiment for ${company}`);
    return result;
});
const results = await Promise.all(promises);
```

This *might* work, but it bypasses `qm`

's built-in task management, error handling within the agent harness, and context propagation. You're effectively losing the benefits of `qm`

as an agent orchestrator.

The error I often hit was `Error: Session ID already exists`

or `Agent 'SentimentAgent' not found in current session scope`

. This happens because when you manually create `new Session()`

inside an `onMessage`

without careful management, you're not correctly extending the parent `session`

's context or properly registering agents within that new, isolated session scope. ** session.task().parallel() is the idiomatic qm way to handle parallel sub-tasks within a single, cohesive workflow.** It ensures context is maintained and agents are properly invoked.

Another pitfall: forgetting to `dotenv.config()`

or incorrectly setting `OPENAI_API_KEY`

. You'll get `Error: Invalid API key provided`

or `Error: connect ECONNREFUSED`

if your LLM client can't reach the API. Basic, but easy to miss when you're focused on **collaborative AI agent architecture**.

The example above uses a fixed `SentimentAgent`

. But what if you need to dynamically pick an agent based on the task? Say, a `FinancialNewsAgent`

vs. a `TechNewsAgent`

.

You can modify the `parallel`

input:

``` js
// Inside ResearchAgent.onMessage
const results = await session.task().parallel(
    companyList.map(company => ({
        // Dynamic agent selection based on company, for example
        agent: company === 'Tesla' ? 'EVAnalystAgent' : 'GeneralSentimentAgent',
        input: `Analyze market sentiment for ${company}`
    }))
).run();
```

This flexibility is crucial for complex **qm agent harness workflow** implementations. It allows your orchestrator agent to make intelligent routing decisions, leading to more specialized and accurate outputs.

**Important:** For this to work, `EVAnalystAgent`

and `GeneralSentimentAgent`

would need to be registered with the `AgentManager`

just like `SentimentAgent`

. This dynamic assignment lets you scale your **building AI teams** with specialized roles easily.

`qm`

handle agent communication overhead?
`qm`

handles communication by serializing messages and managing an internal queue. For agents within the same process (like our Node.js example), this is extremely efficient, relying on direct function calls and event loops. For distributed setups, `qm`

can be extended with custom transport layers (e.g., Redis, Kafka), where overhead depends on your chosen message broker.

`qm`

scale to dozens of agents?
Yes, `qm`

is designed for this. The `AgentManager`

can register many agents, and `session.task().parallel()`

(or `queue()`

) allows you to orchestrate them efficiently. The primary scaling bottleneck usually shifts to your underlying LLM providers (API limits, latency) or the computational resources of the host machine, rather than `qm`

itself.

`qm`

agent failures?
`qm`

provides `session.logs`

and the `onMessage`

method's `session`

object itself can expose debugging info. However, for deeper insights, I always add extensive `console.log`

statements within each agent's `onMessage`

method, along with `try...catch`

blocks. If an agent returns an error string instead of useful output, you can inspect the `session.logs`

(if you configure `qm`

for persistence) or just the console output from the specific agent.

Honestly, setting up effective **building AI teams** isn't just about throwing agents together; it's about thoughtful orchestration and understanding the tools you're using. `qm`

gives you a solid foundation for that, especially once you dig into its parallel execution capabilities. Skip the manual `Promise.all`

for sub-tasks and embrace `session.task().parallel()`

; your latency and mental health will thank you.
