Integration Guide · Phase 4

State Changes & Events

How your app communicates with HUMA - sending events and receiving tool calls.

Overview

HUMA uses an event-driven architecture. Your app sends events when state changes, and HUMA responds with tool calls when it wants to take action.

Your App
Game State
Events + Context
Tool Calls
HUMA
Agent

Events (You → HUMA)

"Something happened in my app, here's the current state"

Tool Calls (HUMA → You)

"I want to do something, please execute this action"

Event Schema

Events are sent via WebSocket using socket.emit('message', ...):

// Event structure
socket.emit('message', {
  type: 'huma-0.1-event',      // Required: identifies this as a HUMA event
  content: {
    name: string,              // Event name (e.g., "player-asked-for-cards")
    context: object,           // Complete current state
    description: string        // Human-readable description
  }
});
FieldTypeDescription
typestringAlways "huma-0.1-event"
content.namestringEvent identifier (kebab-case recommended)
content.contextobjectFull state (from Phase 3)
content.descriptionstringWhat happened, in plain English

Message Events

Chat messages use the new-message event type. Messages can include optional image attachments:

new-message event
socket.emit('message', {
  type: 'new-message',
  content: {
    id: 'msg-123',              // Unique message ID
    content: 'Hello!',          // Message text
    images: [                   // Optional: attached images
      {
        url: 'data:image/png;base64,...',  // Base64 data URL
        alt: 'Screenshot'                   // Optional description
      }
    ],
    participant: {
      id: 'user-1',
      nickname: 'Alice'
    },
    createdAt: '2024-01-15T10:30:00Z',
    type: 'message'
  }
});
FieldTypeDescription
idstringUnique message identifier
contentstringMessage text
imagesImageAttachment[]Optional array of attached images
images[].urlstringBase64 data URL (data:image/png;base64,...)
images[].altstring?Optional description for context
participant.idstringSender's unique ID
participant.nicknamestringSender's display name

Image Support

For detailed information on sending images, including context images and format requirements, see Working with Images.

Event Naming Conventions

Use consistent, descriptive names for your events:

Good Names

  • player-asked-for-cards
  • turn-started
  • message-sent
  • game-ended

Avoid

  • event1- not descriptive
  • UPDATE- too vague
  • playerAskedForCards- use kebab-case
// Define your event names as constants
const EVENT_NAMES = {
  GAME_STARTED: 'game-started',
  TURN_STARTED: 'turn-started',
  PLAYER_ASKED: 'player-asked-for-cards',
  CARDS_RECEIVED: 'cards-received',
  GO_FISH: 'go-fish',
  BOOK_COMPLETED: 'book-completed',
  MESSAGE_SENT: 'message-sent',
  GAME_ENDED: 'game-ended',
};

Writing Good Descriptions

The description tells the agent what just happened. Write it like you're telling a friend:

Be Specific
❌ "Something happened"
✓ "Alice asked Bob for 7s"
Include Actors
❌ "Cards were transferred"
✓ "Bob gave 2 sevens to Alice"
Note Consequences
❌ "Go Fish"
✓ "Bob said 'Go Fish!' - Alice drew a card and gets another turn!"
Keep It Concise
❌ "The player known as Alice decided to ask another player named Bob..."
✓ "Alice asked Bob for Queens"

Example: Go Fish Events

Game Started

socket.emit('message', {
  type: 'huma-0.1-event',
  content: {
    name: 'game-started',
    context: { /* full state */ },
    description: 'Game started! Alice goes first. You have 7 cards.'
  }
});

Player Asked for Cards

socket.emit('message', {
  type: 'huma-0.1-event',
  content: {
    name: 'player-asked-for-cards',
    context: { /* full state */ },
    description: 'Alice asked Bob for 7s.'
  }
});

Go Fish Result

socket.emit('message', {
  type: 'huma-0.1-event',
  content: {
    name: 'go-fish',
    context: { /* full state with updated hands */ },
    description: "Bob said 'Go Fish!' - Alice drew a card."
  }
});

Chat Message

socket.emit('message', {
  type: 'huma-0.1-event',
  content: {
    name: 'message-sent',
    context: { /* full state with new chat message */ },
    description: 'Alice said: "Nice move!"'
  }
});

Responding to Tool Calls

When HUMA sends a tool call, execute the action in your app, then send back the result:

// Receiving a tool call from HUMA
socket.on('event', (data) => {
  if (data.type === 'tool-call') {
    const { toolCallId, toolName, arguments: args } = data;

    // Execute the tool in your app
    try {
      const result = executeToolInYourApp(toolName, args);

      // Send success result
      socket.emit('message', {
        type: 'huma-0.1-event',
        content: {
          type: 'tool-result',
          toolCallId: toolCallId,
          status: 'completed',
          success: true,
          result: result
        }
      });
    } catch (error) {
      // Send error result
      socket.emit('message', {
        type: 'huma-0.1-event',
        content: {
          type: 'tool-result',
          toolCallId: toolCallId,
          status: 'completed',
          success: false,
          error: error.message
        }
      });
    }
  }
});
Important: After a tool executes successfully, you typically send TWO things: (1) the tool result, and (2) a new event with the updated state. For example, after ask_for_cards, send the tool result AND a go-fish or cards-received event.

Tool Result Schema

FieldTypeDescription
typestringAlways "tool-result"
toolCallIdstringID from the tool-call event
status"completed" | "canceled"Execution status
successbooleanWhether execution succeeded
resultany (optional)Result data if successful
errorstring (optional)Error message if failed
// Success result
{ type: 'tool-result', toolCallId: 'tc_123', status: 'completed', success: true, result: 'Go Fish! Drew a 7.' }

// Error result
{ type: 'tool-result', toolCallId: 'tc_123', status: 'completed', success: false, error: 'Not your turn' }

// Canceled result (response to cancel-tool-call)
{ type: 'tool-result', toolCallId: 'tc_123', status: 'canceled', success: false, error: 'Canceled by server' }

Complete Flow Example

Here's what happens during a single turn in Go Fish:

1
You → HUMA: Turn Started Event
name: "turn-started", description: "It's your turn!"
2
HUMA → You: Tool Call
toolName: "ask_for_cards", arguments: { target: "Bob", rank: "7" }
3
Your App: Execute Action
game.askForCards("Finn", "Bob", "7") → Go Fish!
4
You → HUMA: Tool Result + State Event
Tool result: success: true, result: "Go Fish!"Event: name: "go-fish", description: "Bob said Go Fish!"
5
You → HUMA: Next Turn Event
name: "turn-started", description: "It's Bob's turn."

Best Practices

Send events for all meaningful state changes. If a human would notice something changed on screen, send an event.
Include complete state with every event. Don't rely on HUMA remembering previous context.
Write descriptions for a human reader. The agent uses these to understand what's happening.
Don't forget tool results. HUMA waits for tool results before processing new events. Always send a result.
Don't send events too rapidly. If multiple things happen at once, you can batch them into one event with a combined description.
← Design StateNext: Design Tools →