Design State
Define what information your agent has access to - their view of the world.
Overview
State is the information available to HUMA at any moment. In HUMA, state is passed as context with each event and is completely replaced on each update - there's no merging or diffing.
API Schema
State is passed as context in each event:
// Sending an event with context
socket.emit('message', {
type: 'huma-0.1-event',
content: {
name: 'player-asked-for-cards', // Event name
context: { /* YOUR STATE HERE */ }, // Complete current state
description: 'Alice asked Bob for 7s' // Human-readable description
}
});Context Type
type AgentContext = Record<string, unknown>; // Free-form JSON objectContext is a flexible JSON object. You define the structure based on your application's needs.
State Design Principles
1. Human UI Principle
Include everything a human player would see on their screen. If they wouldn't see it, don't include it.
2. Complete Replacement
Every event must include the full current state. HUMA doesn't remember previous context - it's replaced entirely.
3. Clear Ownership
Separate "your info" from "others' info". This prevents confusion about what's private vs public.
4. Decision-Ready Data
Include derived data that helps decision-making. Don't make the agent compute things you can pre-calculate.
What to Include vs Exclude
Include (Agent Can See)
- ✓Agent's own private data (hand, resources)
- ✓Public info about others (card counts, scores)
- ✓Current turn/phase indicator
- ✓Recent chat history
- ✓Last action description
- ✓Game status (ongoing, ended, winner)
- ✓Derived helpers (available choices)
Exclude (Hidden from Agent)
- ✗Other players' private data (their hands)
- ✗Hidden game elements (deck contents)
- ✗Internal IDs (database keys, etc.)
- ✗Full history (causes context overload)
- ✗Agent metadata (that's in personality)
- ✗Future events or hidden timers
Example: Go Fish State Structure
Here's the complete state structure for our Go Fish game, from the perspective of an agent named "Finn":
{
// Game status
"game": {
"isOver": false,
"winner": null,
"deckSize": 24,
"currentTurn": "Finn" // It's Finn's turn to act
},
// Finn's private information (only he can see this)
"you": {
"name": "Finn",
"hand": [
{ "rank": "7", "suit": "hearts" },
{ "rank": "7", "suit": "diamonds" },
{ "rank": "K", "suit": "spades" },
{ "rank": "3", "suit": "clubs" },
{ "rank": "3", "suit": "hearts" },
{ "rank": "A", "suit": "diamonds" }
],
"books": ["Q"], // Finn completed Queens
"availableRanks": ["7", "K", "3", "A"] // Helper for decisions
},
// Other players' PUBLIC info (no hands shown)
"otherPlayers": [
{
"name": "Alice",
"cardCount": 5, // Has 5 cards (we don't know which)
"books": ["J"]
},
{
"name": "Victoria",
"cardCount": 7,
"books": []
}
],
// What just happened
"lastAction": {
"type": "go_fish",
"asker": "Victoria",
"target": "Finn",
"rank": "9",
"description": "Finn said 'Go Fish!' - Victoria drew a card."
},
// Recent chat (last 50 messages)
"chatHistory": [
{ "author": "Alice", "content": "Good luck!", "timestamp": "..." },
{ "author": "Victoria", "content": "May the best player win.", "timestamp": "..." },
{ "author": "Finn", "content": "Let's have fun!", "timestamp": "..." }
]
}Recommended State Structure
This pattern works well for most games and interactive applications:
interface AgentState {
// Core status
game: {
isOver: boolean;
winner: string | null;
currentTurn: string;
// ... other game-wide status
};
// Agent's private view
you: {
name: string;
// ... private data only this agent can see
};
// Other participants' public info
otherPlayers: Array<{
name: string;
// ... only PUBLIC information
}>;
// Recent history
lastAction: {
type: string;
description: string;
// ... action details
} | null;
// Communication
chatHistory: Array<{
author: string;
content: string;
timestamp: string;
}>;
}you makes the state structure consistent regardless of which agent is receiving it. Each agent gets the same structure, just with their own data in the you field.Decision-Ready Data
Include pre-computed data that helps the agent make decisions quickly:
Without Helper Data
"hand": [
{ "rank": "7", "suit": "hearts" },
{ "rank": "7", "suit": "diamonds" },
{ "rank": "K", "suit": "spades" },
{ "rank": "3", "suit": "clubs" }
]
// Agent must scan hand to find
// which ranks it can ask forWith Helper Data
"hand": [
{ "rank": "7", "suit": "hearts" },
{ "rank": "7", "suit": "diamonds" },
{ "rank": "K", "suit": "spades" },
{ "rank": "3", "suit": "clubs" }
],
"availableRanks": ["7", "K", "3"]
// Pre-computed! Agent can
// immediately see valid choicesOther examples: validMoves for chess, canAfford for purchases, isYourTurn boolean.
Bounding Historical Data
Always limit historical data to prevent context overload:
// Good: Bounded chat history
chatHistory: game.chatHistory.slice(-50) // Last 50 messages
// Good: Only recent actions matter
lastAction: game.lastAction // Just the most recent one
// Bad: Unbounded history
chatHistory: game.chatHistory // Could be thousands of messages!
actionHistory: game.allActions // Growing foreverBuilding State from Your Application
Create a function that transforms your app's internal state into the agent-friendly format:
/**
* Build HUMA context for a specific agent
* @param {Game} game - Your game instance
* @param {string} agentId - Which agent is receiving this state
*/
function buildAgentContext(game, agentId) {
const agent = game.getPlayer(agentId);
// Get other players' PUBLIC info only
const otherPlayers = game.players
.filter(p => p.id !== agentId)
.map(p => ({
name: p.name,
cardCount: p.hand.length, // Count, not actual cards!
books: p.books,
}));
return {
game: {
isOver: game.isOver,
winner: game.winner?.name ?? null,
deckSize: game.deck.length,
currentTurn: game.currentPlayer.name,
},
you: {
name: agent.name,
hand: agent.hand, // Full hand for this agent
books: agent.books,
availableRanks: [...new Set(agent.hand.map(c => c.rank))],
},
otherPlayers,
lastAction: game.lastAction,
chatHistory: game.chat.slice(-50),
};
}Context Images
HUMA agents can also access persistent context images stored in their state. These are useful for reference images the agent should always have access to, like game boards or UI screenshots.
// Context images are stored in agent state
interface ContextImage {
imageId: string; // Unique identifier
url: string; // Base64 data URL
description: string; // Description for the agent
category?: string; // Optional category
addedAt: string; // When added
}Context images are managed through WebSocket events:
// Add a context image
socket.emit('message', {
type: 'add-context-image',
content: {
imageId: 'game-board',
url: 'data:image/png;base64,...',
description: 'Current game board state',
category: 'game'
}
});
// Remove a context image
socket.emit('message', {
type: 'remove-context-image',
content: { imageId: 'game-board' }
});Best Practices
you for the agent's data, otherPlayers for others. This helps the agent understand the structure.