Integration Guide · Phase 5

Design Tools

Define what actions your agent can take and handle tool execution.

What Are Tools?

Tools are actions that HUMA agents can perform in your application. You define available tools when creating the agent, and execute them when HUMA sends tool-call events.

1
Define Tools
Create agent with tool definitions
2
Receive Tool Calls
HUMA sends tool-call event via WebSocket
3
Execute & Respond
Run the action, send tool-result back

Tool Definition Schema

Each tool has a name, description, and parameters:

TypeScript
interface ToolDefinition {
  name: string;           // Unique identifier (e.g., "ask_for_cards")
  description: string;    // What the tool does (helps AI decide when to use)
  parameters: ToolParameter[];
}

interface ToolParameter {
  name: string;           // Parameter name (e.g., "targetPlayer")
  type: 'string' | 'number' | 'boolean' | 'object' | 'array';
  description: string;    // What this parameter is for
  required?: boolean;     // Is this parameter required? (default: false)
}

Description Matters

The description helps the AI decide when to use the tool. Be specific about purpose and conditions: "Ask another player for cards. Only use when it's your turn."

Example: Go Fish Tools

For our Go Fish game, we define two tools:

ask_for_cards— Main game action
const ASK_FOR_CARDS_TOOL = {
  name: 'ask_for_cards',
  description:
    'Ask another player for all their cards of a specific rank. ' +
    'You must already have at least one card of that rank in your hand. ' +
    'Only use this when it is your turn.',
  parameters: [
    {
      name: 'targetPlayer',
      type: 'string',
      description: 'The name of the player to ask',
      required: true,
    },
    {
      name: 'rank',
      type: 'string',
      description: 'The card rank to ask for (e.g., "7", "K", "A")',
      required: true,
    },
  ],
};
send_message— Chat tool
const SEND_MESSAGE_TOOL = {
  name: 'send_message',
  description:
    'Send a chat message to all players. Use for reactions, comments, ' +
    'or friendly conversation during the game.',
  parameters: [
    {
      name: 'message',
      type: 'string',
      description: 'The message to send',
      required: true,
    },
  ],
};

Pass these to metadata.tools when creating your agent:

const GO_FISH_TOOLS = [ASK_FOR_CARDS_TOOL, SEND_MESSAGE_TOOL];

// When creating the agent:
const metadata = {
  className: 'Finn',
  personality: FINN_PERSONALITY,
  instructions: FINN_INSTRUCTIONS,
  tools: GO_FISH_TOOLS,  // <-- Tools array goes here
};

Receiving Tool Calls

When HUMA decides to use a tool, it sends a tool-call event:

tool-call event
// HUMA → Your App
{
  type: 'tool-call',
  toolCallId: 'tc_abc123',      // Unique ID - save this!
  toolName: 'ask_for_cards',    // Which tool to execute
  arguments: {                   // Arguments passed by the agent
    targetPlayer: 'Victoria',
    rank: '7'
  }
}

Save the toolCallId

You must include the toolCallId in your result. HUMA uses it to match results with pending calls.

Executing Tools

When you receive a tool-call, validate inputs, execute the action, and return a result:

Tool Handler
function handleToolCall(socket, game, agentPlayerId, toolCall) {
  const { toolCallId, toolName, arguments: args } = toolCall;

  switch (toolName) {
    case 'ask_for_cards':
      return executeAskForCards(socket, game, agentPlayerId, toolCallId, args);

    case 'send_message':
      return executeSendMessage(socket, game, agentPlayerId, toolCallId, args);

    default:
      // Unknown tool - return error
      socket.emit('message', {
        type: 'huma-0.1-event',
        content: {
          type: 'tool-result',
          toolCallId,
          status: 'completed',
          success: false,
          error: `Unknown tool: ${toolName}`
        }
      });
  }
}

Validation Example

Always validate before executing:

function executeAskForCards(socket, game, agentPlayerId, toolCallId, args) {
  const { targetPlayer, rank } = args;

  // 1. Validate it's the agent's turn
  const currentPlayer = game.getCurrentPlayer();
  if (currentPlayer.id !== agentPlayerId) {
    return sendError(socket, toolCallId,
      `It's not your turn. It's ${currentPlayer.name}'s turn.`);
  }

  // 2. Validate target player exists
  const target = game.getPlayerByName(targetPlayer);
  if (!target) {
    return sendError(socket, toolCallId,
      `Player "${targetPlayer}" not found.`);
  }

  // 3. Validate agent has the rank they're asking for
  const agentPlayer = game.getPlayer(agentPlayerId);
  const hasRank = agentPlayer.hand.some(card => card.rank === rank);
  if (!hasRank) {
    return sendError(socket, toolCallId,
      `You don't have any ${rank}s. You can only ask for ranks you have.`);
  }

  // All valid - execute the game action
  const result = game.askForCards(agentPlayerId, target.id, rank);

  // ... send events and result ...
}

Sending Tool Results

After executing a tool, send back a result:

Success Result
{
  type: 'huma-0.1-event',
  content: {
    type: 'tool-result',
    toolCallId: 'tc_abc123',
    status: 'completed',
    success: true,
    result: 'Victoria gave you 2 seven(s)!'
  }
}
Error Result
{
  type: 'huma-0.1-event',
  content: {
    type: 'tool-result',
    toolCallId: 'tc_abc123',
    status: 'completed',
    success: false,
    error: "It's not your turn."
  }
}

Result Helpers

function createToolResultSuccess(toolCallId, result) {
  return {
    type: 'huma-0.1-event',
    content: {
      type: 'tool-result',
      toolCallId,
      status: 'completed',
      success: true,
      result,
    },
  };
}

function createToolResultError(toolCallId, error) {
  return {
    type: 'huma-0.1-event',
    content: {
      type: 'tool-result',
      toolCallId,
      status: 'completed',
      success: false,
      error,
    },
  };
}

Event Sequencing

When a tool changes game state, send context update events before the tool result. This ensures the agent sees the updated state with the result:

Correct Sequence
// 1. Execute the game action
const result = game.askForCards(agentId, targetId, rank);

// 2. Send state-updating events FIRST
socket.emit('message', {
  type: 'huma-0.1-event',
  content: {
    name: 'cards-received',
    context: buildAgentContext(game, agentId),  // Fresh state
    description: 'Victoria gave 2 seven(s) to Finn!'
  }
});

// 3. THEN send tool result
socket.emit('message', {
  type: 'huma-0.1-event',
  content: {
    type: 'tool-result',
    toolCallId: 'tc_abc123',
    status: 'completed',
    success: true,
    result: 'Victoria gave you 2 seven(s)! Your turn continues.'
  }
});

Why Events Before Results?

The agent processes messages in order. If you send the result first, the agent might decide its next action based on stale state. Sending events first ensures the agent sees the current game state when processing the result.

Tool Cancellation

HUMA may send cancel-tool-call to abort a pending tool:

cancel-tool-call event
// HUMA → Your App
{
  type: 'cancel-tool-call',
  toolCallId: 'tc_abc123',
  reason: 'User interrupted'  // Optional
}

Handle cancellation by responding with status: 'canceled':

function handleCancelToolCall(socket, toolCallId, reason) {
  // If the tool is still running, stop it
  // (For instant tools like Go Fish, just acknowledge)

  socket.emit('message', {
    type: 'huma-0.1-event',
    content: {
      type: 'tool-result',
      toolCallId,
      status: 'canceled',  // <-- Important!
      success: false,
      error: reason || 'Canceled by agent'
    }
  });
}

Race Condition Note

If you receive a cancel for a tool that already completed, you can safely ignore it. The agent handles this gracefully.

Tool Design Best Practices

1Use Clear, Action-Oriented Names

Names should be verbs describing what happens:

Good
ask_for_cards
send_message
place_bet
Avoid
action
do_thing
execute

2Write Helpful Descriptions

Include what it does, when to use it, and any constraints:

✗ "Ask for cards"
✓ "Ask another player for all their cards of a specific rank. You must already have at least one card of that rank. Only use when it's your turn."

3Validate All Inputs

Never trust input blindly. Check:

  • • Required parameters exist
  • • Values are within valid ranges
  • • Action is allowed in current game state
  • • Player has permission to perform action

4Return Useful Error Messages

Help the agent understand what went wrong and how to fix it:

✗ "Invalid input"
✓ "You don't have any 7s. You can only ask for ranks you have: K, 3, A"

5Match Tool to Instructions

Tool definitions tell the AI what tools exist. Instructions tell it when to use them. Make sure both are aligned:

Tool def: "Only use when it's your turn"
Instructions: "When it's your turn, use ask_for_cards..."

Complete Flow Example

Here's the complete flow when Finn asks Victoria for 7s:

1. HUMA sends tool-call
{
  type: 'tool-call',
  toolCallId: 'tc_abc123',
  toolName: 'ask_for_cards',
  arguments: { targetPlayer: 'Victoria', rank: '7' }
}
2. Your app validates and executes
// Validate
if (currentPlayer.id !== agentPlayerId) return error("Not your turn");
if (!hasRank(agentPlayer, '7')) return error("You don't have 7s");

// Execute
const result = game.askForCards('finn', 'victoria', '7');
// result: { cardsReceived: 2, bookCompleted: false }
3. Send context update event
socket.emit('message', {
  type: 'huma-0.1-event',
  content: {
    name: 'cards-received',
    context: buildAgentContext(game, 'finn'),
    description: 'Victoria gave 2 seven(s) to Finn. Finn gets another turn!'
  }
});
4. Send tool result
socket.emit('message', {
  type: 'huma-0.1-event',
  content: {
    type: 'tool-result',
    toolCallId: 'tc_abc123',
    status: 'completed',
    success: true,
    result: 'Victoria gave you 2 seven(s)! Your turn continues.'
  }
});

Common Pitfalls

Forgetting the toolCallId

Always include toolCallId in results. Without it, HUMA can't match results to pending calls.

No input validation

Never execute tools blindly. Validate all inputs against current game state. Return helpful errors when validation fails.

Vague tool descriptions

"Do something with cards" doesn't help the AI. Be specific: what it does, when to use it, what constraints exist.

Wrong event order

Send context updates before tool results. The agent needs current state to make good decisions.

Not handling unknown tools

Always have a default case that returns an error for unknown tool names.