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.
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
}
});| Field | Type | Description |
|---|---|---|
| type | string | Always "huma-0.1-event" |
| content.name | string | Event identifier (kebab-case recommended) |
| content.context | object | Full state (from Phase 3) |
| content.description | string | What happened, in plain English |
Message Events
Chat messages use the new-message event type. Messages can include optional image attachments:
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'
}
});| Field | Type | Description |
|---|---|---|
| id | string | Unique message identifier |
| content | string | Message text |
| images | ImageAttachment[] | Optional array of attached images |
| images[].url | string | Base64 data URL (data:image/png;base64,...) |
| images[].alt | string? | Optional description for context |
| participant.id | string | Sender's unique ID |
| participant.nickname | string | Sender'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-cardsturn-startedmessage-sentgame-ended
Avoid
event1- not descriptiveUPDATE- too vagueplayerAskedForCards- 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:
✓ "Alice asked Bob for 7s"
✓ "Bob gave 2 sevens to Alice"
✓ "Bob said 'Go Fish!' - Alice drew a card and gets another turn!"
✓ "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
}
});
}
}
});ask_for_cards, send the tool result AND a go-fish or cards-received event.Tool Result Schema
| Field | Type | Description |
|---|---|---|
| type | string | Always "tool-result" |
| toolCallId | string | ID from the tool-call event |
| status | "completed" | "canceled" | Execution status |
| success | boolean | Whether execution succeeded |
| result | any (optional) | Result data if successful |
| error | string (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:
name: "turn-started", description: "It's your turn!"toolName: "ask_for_cards", arguments: { target: "Bob", rank: "7" }game.askForCards("Finn", "Bob", "7") → Go Fish!Tool result: success: true, result: "Go Fish!"Event: name: "go-fish", description: "Bob said Go Fish!"name: "turn-started", description: "It's Bob's turn."