Features

Messaging

Human-like typing simulation with natural delays, typing indicators, and burst messaging.

Enable Messaging

Add messaging to the agent's addons:

metadata: {
  className: 'ChatBot',
  personality: 'A friendly assistant.',
  instructions: 'Respond using send_message tool.',
  tools: [],
  addons: {
    enabled: ['messaging'],
  },
}

This gives the agent a send_message tool. Messages go through a realistic pipeline: reading delay, typing indicator, then delivery.

send_message Tool

The agent calls this tool to send messages. It accepts an array of message objects for burst messaging (up to 10 per call).

tool schema
{
  name: 'send_message',
  parameters: [{
    name: 'messages',
    type: 'array',
    required: true,
    items: {
      type: 'object',
      properties: {
        message:    { type: 'string', description: 'The message content' },
        extra_args: { type: 'object', description: 'Additional parameters passed through to client' },
      },
      required: ['message'],
    },
  }],
}
FieldTypeDescription
messagesarrayArray of message objects. 1-10 items.
messages[].messagestringThe message text content. Required.
messages[].extra_argsobject?Arbitrary metadata passed through to all client events. Optional.

Each message in the array is delivered sequentially with its own typing cycle. A new send_message call automatically cancels any pending burst.

Server Events

Events emitted to the client via socket.on('event', ...). All messaging events have type: 'messaging'.

typing_start

Emitted when the agent starts typing a message. Show a typing indicator.

typing_start
{
  type: 'messaging',
  event: 'typing_start',
  extra_args?: Record<string, unknown>  // From the message being typed
}

message_sent

Emitted when a message has been "typed" and is ready to display. Hide the typing indicator and show the message.

message_sent
{
  type: 'messaging',
  event: 'message_sent',
  toolCallId: string,                   // ID for the entire burst
  message: string,                      // The message content
  messageIndex: number,                 // 0-based index within the burst
  messageCount: number,                 // Total messages in the burst
  extra_args?: Record<string, unknown>  // From the corresponding message object
}

typing_end

Emitted when typing stops without a message being sent (cancellation). Hide the typing indicator.

typing_end
{
  type: 'messaging',
  event: 'typing_end',
  reason: 'completed' | 'canceled',
  extra_args?: Record<string, unknown>
}
EventWhenClient Action
typing_startAfter reading delay, before each messageShow typing indicator
message_sentAfter typing delay completesHide indicator, display message, send confirmation
typing_endWhen pending message is canceledHide typing indicator

Handling Events

socket.on('event', (data) => {
  if (data.type === 'messaging') {
    switch (data.event) {
      case 'typing_start':
        showTypingIndicator();
        break;
      case 'message_sent':
        hideTypingIndicator();
        displayMessage(data.message, data.extra_args);
        // Send addon-tool-event confirmation (see next section)
        socket.emit('message', {
          type: 'addon-tool-event',
          toolCallId: data.toolCallId,
          data: { messageIndex: data.messageIndex, success: true },
        });
        break;
      case 'typing_end':
        hideTypingIndicator();
        break;
    }
  }
});

Client Confirmation

Send an addon-tool-event for each message_sent event you receive. The server tracks all confirmations and reports the tool result back to the agent only after every message in the burst has been confirmed.

addon-tool-event schema
socket.emit('message', {
  type: 'addon-tool-event',
  toolCallId: string,     // From the message_sent event
  data: {
    messageIndex: number,   // From the message_sent event
    success: boolean,       // Whether the message was handled successfully
    context?: object,       // Optional context merged into the tool result
    error?: string,         // Error message if success is false
  },
});
FieldTypeDescription
typestringAlways "addon-tool-event"
toolCallIdstringThe toolCallId from message_sent
data.messageIndexnumberThe messageIndex from message_sent (0-based)
data.successbooleanWhether the client successfully processed this message
data.contextobject?Optional context merged into the tool result event
data.errorstring?Error message if success is false

Send one confirmation per message_sent event. The agent won't receive the tool result until all messages in the burst are confirmed. If you send success: false for any message, the agent will see which messages failed and can retry or adjust.

Configuration

Customize timing behavior in the agent metadata:

addons: {
  enabled: ['messaging'],
  messaging: {
    extraInstructions: 'Always reply formally.',  // Appended to tool description
    delays: {
      readingDelayMs: 2000,       // Pause before typing (default: 3000)
      typingMode: 'exponential',  // 'linear' or 'exponential' (default: 'linear')
      typingWpm: 70,              // Words per minute (default: 90)
      exponentialScaling: 0.5,    // Scaling for exponential mode (default: 0.5)
      minTypingDelayMs: 500,      // Floor for typing delay (default: 500)
      maxTypingDelayMs: 30000,    // Cap for typing delay (default: 30000)
      burstPauseMs: 200,          // Pause between burst messages (default: 200)
    },
  },
}
ParameterDefaultDescription
extraInstructions-Extra text appended to the send_message tool description the agent sees
readingDelayMs3000Pause before typing starts (simulates reading)
typingMode"linear"linear: constant WPM. exponential: longer messages take disproportionately longer
typingWpm90Base typing speed in words per minute
exponentialScaling0.5Scaling factor for exponential mode. Higher = more aggressive slowdown on long messages
minTypingDelayMs500Minimum typing delay regardless of message length
maxTypingDelayMs30000Maximum typing delay cap
burstPauseMs200Pause between consecutive messages in a burst

Cancellation

When a new event arrives while a message is pending, the pending message is automatically canceled (you'll receive a typing_end with reason: 'canceled'). Use skipCancelPendingMessage for background events that shouldn't interrupt typing:

socket.emit('message', {
  type: 'context-update',
  triggering: false,
  name: 'background-update',
  context: { ... },
  description: 'Background status update',
  skipCancelPendingMessage: true,  // Won't interrupt typing
});

extra_args

Each message object can include an extra_args object. This is passed through untouched to all messaging events (typing_start, message_sent, typing_end). Use it to attach client-side metadata like reply references, message formatting, or UI hints.

To make the agent use extra_args, describe the expected structure in extraInstructions:

agent config
addons: {
  enabled: ['messaging'],
  messaging: {
    extraInstructions: `When replying to a specific message, include:
extra_args: { replyToId: "" }
Only use replyToId when directly responding to a specific message.`,
  },
}

The agent will then include extra_args when appropriate, and you'll receive it in the message_sent event:

received event
// message_sent event with extra_args
{
  type: 'messaging',
  event: 'message_sent',
  toolCallId: 'tc_abc123',
  message: 'Great point! I agree.',
  messageIndex: 0,
  messageCount: 1,
  extra_args: { replyToId: 'msg-42' }
}

Full Example: Chat with Replies

Complete integration showing agent creation, WebSocket connection, messaging events, confirmation, and reply support via extra_args.

1. Create Agent

const response = await fetch('https://api.humalike.tech/api/agents', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-API-Key': apiKey,
  },
  body: JSON.stringify({
    name: 'Chat Agent',
    agentType: 'HUMA-0.1',
    metadata: {
      className: 'ChatBot',
      personality: 'A friendly assistant who loves to chat.',
      instructions: 'Respond using send_message. Be conversational.',
      tools: [],
      routerType: 'conversational',
      addons: {
        enabled: ['messaging'],
        messaging: {
          extraInstructions: `When replying to a specific message, include:
extra_args: { replyToId: "" }
Only use replyToId when directly responding to a specific message.`,
          delays: {
            readingDelayMs: 1500,
            typingMode: 'exponential',
            typingWpm: 80,
          },
        },
      },
    },
  }),
});

const agent = await response.json();

2. Connect WebSocket

const socket = io('wss://api.humalike.tech', {
  query: { agentId: agent.id, apiKey },
  transports: ['websocket'],
});

3. Handle Messaging Events

socket.on('event', (data) => {
  if (data.type === 'messaging') {
    switch (data.event) {
      case 'typing_start':
        showTypingIndicator();
        break;

      case 'message_sent':
        hideTypingIndicator();

        // Render message (with reply reference if present)
        const replyToId = data.extra_args?.replyToId;
        displayMessage({
          id: crypto.randomUUID(),
          text: data.message,
          author: 'agent',
          replyTo: replyToId || null,
        });

        // Confirm delivery for this message
        socket.emit('message', {
          type: 'addon-tool-event',
          toolCallId: data.toolCallId,
          data: { messageIndex: data.messageIndex, success: true },
        });
        break;

      case 'typing_end':
        hideTypingIndicator();
        break;
    }
  }
});

4. Send User Messages

function sendMessage(text) {
  const messageId = crypto.randomUUID();

  displayMessage({ id: messageId, text, author: 'user' });

  socket.emit('message', {
    type: 'context-update',
    triggering: true,
    name: 'new-message',
    context: {
      chatHistory: [{
        id: messageId,
        author: 'User',
        content: text,
        timestamp: new Date().toISOString(),
      }],
      user: { name: 'User' },
    },
    description: `User sent: "${text}"`,
  });
}