Skip to content

Dances (Custom Tools)

Dances are ESM modules that extend what agents can do. They provide custom tools (like game logic, state machines, or domain-specific operations) and injection functions that shape agent behavior.

A dance file exports two things:

  1. inject(context) — Returns a string injected into the agent’s prompt before each LLM call
  2. Named tool exports — Objects with description, params, and handler
my-dance.js
export function inject({ state, agent }) {
const phase = state.get('phase') || 'waiting';
return `Current phase: ${phase}. Your role: ${agent.role}.`;
}
export const do_something = {
description: 'Perform an action in the game',
params: {
action: { type: 'string', description: 'The action to take' },
},
handler: async ({ args, state, agent, acp }) => {
await acp.setState('last_action', args.action);
await acp.publish('action.taken', { action: args.action, by: agent.role });
return { result: { success: true, action: args.action } };
},
};

Every tool handler receives:

FieldTypeDescription
argsobjectArguments passed by the LLM
stateMapCurrent shared state (read-only snapshot)
agent{ role, agentId }The calling agent’s identity
acpobjectCoordination methods
MethodDescription
acp.publish(type, data)Publish an event
acp.claim(resource)Claim a resource
acp.release(resource)Release a claim
acp.setState(key, value)Set shared state

Handlers return { result } or { error }:

// Success
return { result: { score: 42, message: 'Move accepted' } };
// Error
return { error: 'Invalid move: king in check' };

Optionally include wait to control agent sleep:

// Agent sleeps until a matching event
return { result: { success: true }, wait: 'opponent.moved' };
// Agent stays awake
return { result: { success: true }, wait: false };

inject() is called before each LLM turn. Use it to provide dynamic context:

export function inject({ state, agent }) {
const board = state.get('board');
const turn = state.get('current_turn');
const myColor = agent.role === 'white' ? 'White' : 'Black';
if (turn !== myColor.toLowerCase()) {
return 'It is not your turn. Say "Waiting" with no tool calls.';
}
return `You are playing ${myColor}. The board:\n${board}\nMake your move.`;
}

Important: For non-active agents, the injection should say 'Say "Waiting" with no tool calls.' Never use “DONE” — the runner terminates on that keyword.

hives:
main:
dances: ./my-dance.js
acp: spec.acp.yaml
Terminal window
incubator --port=8080 --dances=./my-dance.js

UIs can call dance tools via WebSocket:

ws.send(JSON.stringify({
type: 'dance_call',
tool: 'do_something',
args: { action: 'attack' },
agentId: 'player-1',
role: 'warrior',
callId: 'call-123',
}));
// Response:
// { type: 'dance_result', callId: 'call-123', result: { success: true } }

Or via REST:

Terminal window
curl -X POST http://localhost:8080/api/dance/do_something \
-H 'Content-Type: application/json' \
-d '{"args": {"action": "attack"}, "agentId": "player-1", "role": "warrior"}'

The chess demo uses a dance file with ~300 lines of game logic:

export const make_move = {
description: 'Make a chess move',
params: {
from: { type: 'string', description: 'Source square (e.g., e2)' },
to: { type: 'string', description: 'Target square (e.g., e4)' },
},
handler: async ({ args, state, agent, acp }) => {
const board = JSON.parse(state.get('board'));
const turn = state.get('current_turn');
// Validate it's this player's turn
if (turn !== agent.role) {
return { error: `Not your turn. Current turn: ${turn}` };
}
// Validate and execute the move
const result = executeMove(board, args.from, args.to);
if (result.error) return { error: result.error };
// Update state
await acp.setState('board', JSON.stringify(result.board));
await acp.setState('current_turn', turn === 'white' ? 'black' : 'white');
// Publish turn event (wakes the other player)
await acp.publish(`turn.${turn === 'white' ? 'black' : 'white'}`, {
move: `${args.from}-${args.to}`,
});
return { result: { move: `${args.from}-${args.to}`, board: renderBoard(result.board) } };
},
};

See the Demos Guide for running the full chess and werewolf demos.