Integration Guide · Phase 3

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.

Key Concept: Context is fully replaced on each event. Every event you send must include the complete current state, not just what changed.
The Human UI Principle: Design state by imagining a human player. What would they see on their screen? That's your state. If a human wouldn't see it, the agent shouldn't either.

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 object

Context 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;
  }>;
}
Why "you" instead of the agent's name? Using 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 for

With 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 choices

Other 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 forever
Why 50 messages? It's a practical limit that provides enough context for the agent to understand the conversation flow without overwhelming the context window. Adjust based on your message length and frequency.

Building 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' }
});
Learn More: For complete details on working with images, including inline message images and format requirements, see Working with Images.

Best Practices

Test with the Human UI principle. Ask yourself: "Would a human player see this?" If not, remove it from the state.
Use consistent field names. Always use you for the agent's data, otherPlayers for others. This helps the agent understand the structure.
Always send complete state. Even if only one thing changed, send the entire state. HUMA doesn't diff - it replaces.
Don't leak hidden information. Never include other players' private data, even accidentally. Double-check your state builder.
Don't include unbounded arrays. Always slice/limit historical data. A context with 10,000 messages will perform poorly.
← Design RulesNext: State Changes & Events →