# How the Agent-to-Agent Messaging System Works in Prime Agent

> Discover how Prime Agent's agent-to-agent messaging system works. Learn about its dual-queue architecture and LLM loop integration for seamless communication.

- Repository: [Prime Intellect/prime-agent](https://github.com/PrimeIntellect-ai/prime-agent)
- Tags: internals
- Published: 2026-08-09

---

**Prime Agent enables agent-to-agent messaging through a dual-queue architecture where messages are queued via `steer()` or `followUp()` APIs and injected into the LLM loop before or after the current conversation turn.**

The Prime Agent framework from PrimeIntellect-ai provides a lightweight, queue-based mechanism for inter-agent communication that allows one `Agent` instance to send messages to another without dedicated inter-process protocols. By leveraging the `PendingMessageQueue` system and the canonical `AgentMessage` type, agents can coordinate multi-turn conversations while maintaining isolated transcripts and state.

## Core Architecture of the Messaging System

The messaging infrastructure revolves around standardized message objects and two distinct queues that control when messages enter the conversation flow.

### AgentMessage Schema

All communication uses the **`AgentMessage`** type defined in [`packages/agent/src/types.ts`](https://github.com/PrimeIntellect-ai/prime-agent/blob/main/packages/agent/src/types.ts). This canonical shape includes properties for `role`, `content`, `timestamp`, and other metadata. Messages can assume four distinct roles:

- **`user`** – Input from the human operator or another agent
- **`assistant`** – Responses generated by the LLM
- **`toolResult`** – Output from tool executions
- **`custom`** – Application-specific message types

This unified schema ensures that any agent can consume messages produced by another agent without translation layers.

### The Dual-Queue Design

Each `Agent` instance maintains two `PendingMessageQueue` instances created in the constructor at [`packages/agent/src/agent.ts`](https://github.com/PrimeIntellect-ai/prime-agent/blob/main/packages/agent/src/agent.ts) (lines 20-35):

1. **Steering Queue** – Holds messages to be processed **immediately after the current assistant turn finishes**. Created with `new PendingMessageQueue(options.steeringMode ?? "one-at-a-time")`, this queue ensures high-priority messages interrupt the flow before the next LLM generation.

2. **Follow-up Queue** – Holds messages that execute **only after the agent would otherwise stop**. This queue handles background tasks or secondary conversations that should not block the primary interaction loop.

## Message Queuing APIs

Prime Agent exposes public methods to populate these queues, allowing external code (including other agents) to inject messages programmatically.

### The steer() Method

The **`steer()`** method (lines 84-92 in [`packages/agent/src/agent.ts`](https://github.com/PrimeIntellect-ai/prime-agent/blob/main/packages/agent/src/agent.ts)) pushes one or more messages onto the steering queue. When called, the method immediately places the `AgentMessage` objects into the queue, making them available for the next iteration of the agent loop.

```typescript
// Push a high-priority message to be processed next
agentA.steer({
  role: "user",
  content: "Please analyze this data from Agent B",
  timestamp: Date.now()
});

```

### The followUp() Method

The **`followUp()`** method operates identically to `steer()` but targets the follow-up queue instead. Messages added here wait until the current conversation naturally concludes before executing.

## Queue Draining and Lifecycle

The agent loop processes queued messages at specific points in the execution lifecycle, ensuring deterministic ordering of inter-agent communication.

### How continue() Processes Queued Messages

When **`continue()`** is invoked, the agent drains queues in strict priority order (lines 69-81 in [`packages/agent/src/agent.ts`](https://github.com/PrimeIntellect-ai/prime-agent/blob/main/packages/agent/src/agent.ts)):

```typescript
// Pseudocode representing the actual implementation
async continue() {
  // 1. Process steering messages first
  const steeringMessages = await this.steeringQueue.drain();
  for (const msg of steeringMessages) {
    this._state.messages.push(msg);
  }
  
  // 2. Run the LLM loop with injected messages
  
  // 3. Only drain follow-up if the agent would stop
  if (shouldStop) {
    const followUpMessages = await this.followUpQueue.drain();
    // Process follow-ups...
  }
}

```

This guarantees that **steering messages are injected before the next assistant response**, while follow-up messages run only after the current conversational thread ends.

### Message Flow in the Agent Loop

The low-level loop functions `runAgentLoop` and `runAgentLoopContinue` (referenced in lines 65-78 of [`packages/agent/src/agent.ts`](https://github.com/PrimeIntellect-ai/prime-agent/blob/main/packages/agent/src/agent.ts)) handle the actual consumption:

1. The loop checks for queued messages via `this.steeringQueue.drain()`
2. Messages are appended to the internal transcript: `this._state.messages.push(event.message)`
3. Lifecycle events (`agent_end`, `turn_end`) emit to subscribers, allowing other agents to react

## Implementing Agent-to-Agent Communication

To establish communication between two agents, one agent queues messages that the other consumes through its standard prompt/continue flow. Because each agent owns its own transcript and queue instances, the agents remain isolated yet can exchange `AgentMessage` objects through the shared runtime.

```typescript
import { Agent } from "@earendil-works/pi-agent";

// Create two agents with separate models or the same one
const agentA = new Agent({ model: myModelA });
const agentB = new Agent({ model: myModelB });

// Agent A sends a steering message that Agent B will later consume
await agentA.prompt("Hello, Agent B!");

// Forward the assistant reply from A to B
agentA.subscribe(async (event) => {
  if (event.type === "message_end" && event.message.role === "assistant") {
    // Queue the reply as a user message for B
    agentB.steer(event.message);
  }
});

// Start B so it will process the queued steering message
await agentB.continue();   // B will see A's reply as its next input

```

This pattern works both in-process and across network boundaries when combined with the `streamProxy` function from [`packages/agent/src/proxy.ts`](https://github.com/PrimeIntellect-ai/prime-agent/blob/main/packages/agent/src/proxy.ts), which routes LLM calls through remote services.

## Key Source Files

The agent-to-agent messaging system spans four critical files in the Prime Agent repository:

- **[`packages/agent/src/agent.ts`](https://github.com/PrimeIntellect-ai/prime-agent/blob/main/packages/agent/src/agent.ts)** – Core `Agent` class implementation containing queue initialization, the `steer()` and `followUp()` public APIs, and the `continue()` method that drains queues
- **[`packages/agent/src/types.ts`](https://github.com/PrimeIntellect-ai/prime-agent/blob/main/packages/agent/src/types.ts)** – TypeScript definitions for `AgentMessage`, `AgentState`, and the message schema that enables cross-agent compatibility
- **[`packages/agent/src/agent-loop.ts`](https://github.com/PrimeIntellect-ai/prime-agent/blob/main/packages/agent/src/agent-loop.ts)** – Low-level loop implementation that consumes queued messages, drives the LLM stream, and emits lifecycle events
- **[`packages/agent/src/proxy.ts`](https://github.com/PrimeIntellect-ai/prime-agent/blob/main/packages/agent/src/proxy.ts)** – `streamProxy` helper for distributed setups where agents communicate across network boundaries

## Summary

- Prime Agent uses a **dual-queue architecture** (steering and follow-up) to manage when messages enter the conversation flow
- The **`AgentMessage`** type standardizes communication across all agents with support for user, assistant, toolResult, and custom roles
- **`steer()`** injects messages before the next LLM turn, while **`followUp()`** defers messages until after the conversation ends
- Queue draining occurs in **`continue()`** at lines 69-81 of [`packages/agent/src/agent.ts`](https://github.com/PrimeIntellect-ai/prime-agent/blob/main/packages/agent/src/agent.ts), ensuring deterministic message ordering
- Agents communicate by calling queue methods on each other's instances, with no dedicated inter-process protocol required

## Frequently Asked Questions

### What is the difference between steer() and followUp() in Prime Agent?

The `steer()` method pushes messages onto the steering queue, which drains immediately after the current assistant turn finishes but before the next LLM generation begins. The `followUp()` method pushes messages onto the follow-up queue, which only drains after the agent determines it would otherwise stop, making it suitable for background tasks or secondary conversation threads.

### How does AgentMessage structure support different message types?

The `AgentMessage` interface defined in [`packages/agent/src/types.ts`](https://github.com/PrimeIntellect-ai/prime-agent/blob/main/packages/agent/src/types.ts) uses a `role` discriminator field that accepts `"user"`, `"assistant"`, `"toolResult"`, or `"custom"` values. This schema allows agents to exchange not only conversational text but also structured tool outputs and application-specific metadata through the same queuing system.

### Can agents communicate across network boundaries?

Yes. While the core messaging system operates through in-memory queue objects, the `streamProxy` function in [`packages/agent/src/proxy.ts`](https://github.com/PrimeIntellect-ai/prime-agent/blob/main/packages/agent/src/proxy.ts) enables distributed deployments where one agent routes LLM calls through a remote service. Agents can still exchange `AgentMessage` objects across these boundaries because the protocol relies on the serializable message schema rather than shared memory.

### Where is the message queue drained in the agent lifecycle?

The queues drain inside the `continue()` method at [`packages/agent/src/agent.ts`](https://github.com/PrimeIntellect-ai/prime-agent/blob/main/packages/agent/src/agent.ts) (lines 69-81). The implementation first calls `this.steeringQueue.drain()` to process high-priority messages, runs the LLM loop, then conditionally calls `this.followUpQueue.drain()` only if the agent reaches a stopping state. This sequencing ensures steering messages always preempt follow-up messages.