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.
Tool Definition Schema
Each tool has a name, description, and parameters:
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 actionconst 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 toolconst 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:
// 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:
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:
{
type: 'huma-0.1-event',
content: {
type: 'tool-result',
toolCallId: 'tc_abc123',
status: 'completed',
success: true,
result: 'Victoria gave you 2 seven(s)!'
}
}{
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:
// 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:
// 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:
ask_for_cardssend_messageplace_betactiondo_thingexecute2Write Helpful Descriptions
Include what it does, when to use it, and any constraints:
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:
5Match Tool to Instructions
Tool definitions tell the AI what tools exist. Instructions tell it when to use them. Make sure both are aligned:
Instructions: "When it's your turn, use ask_for_cards..."
Complete Flow Example
Here's the complete flow when Finn asks Victoria for 7s:
{
type: 'tool-call',
toolCallId: 'tc_abc123',
toolName: 'ask_for_cards',
arguments: { targetPlayer: 'Victoria', rank: '7' }
}// 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 }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!'
}
});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.