Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/cloudflare/agents/llms.txt

Use this file to discover all available pages before exploring further.

A simple demo showing a smart agent playing Tic Tac Toe against a human. The AI opponent uses GPT-4o to make strategic moves with persistent game state.

What it demonstrates

  • Callable methods - makeMove and clearBoard as RPC endpoints
  • Persistent state - Game board survives restarts
  • AI integration - Uses OpenAI’s GPT-4o for strategic gameplay
  • State validation - Checks for valid moves and win conditions
  • React integration - Real-time UI updates with useAgent

Server Implementation

src/server.ts
import { openai } from "@ai-sdk/openai";
import { Agent, callable, routeAgentRequest } from "agents";
import { generateObject } from "ai";
import { z } from "zod";

type Player = "X" | "O";
type Played = Player | null;

export type TicTacToeState = {
  board: [
    [Played, Played, Played],
    [Played, Played, Played],
    [Played, Played, Played]
  ];
  currentPlayer: Player;
  winner: Player | null;
};

export class TicTacToe extends Agent<Env, TicTacToeState> {
  initialState: TicTacToeState = {
    board: [
      [null, null, null],
      [null, null, null],
      [null, null, null]
    ],
    currentPlayer: "X",
    winner: null
  };

  @callable()
  async makeMove(move: [number, number], player: Player) {
    if (this.state.currentPlayer !== player) {
      throw new Error("It's not your turn");
    }
    const [row, col] = move;
    if (this.state.board[row][col] !== null) {
      throw new Error("Cell already played");
    }
    
    // Make a copy of the board
    const board: TicTacToeState["board"] = this.state.board.map((row) =>
      row.map((cell) => cell)
    ) as TicTacToeState["board"];
    
    board[row][col] = player;
    
    this.setState({
      ...this.state,
      board,
      currentPlayer: player === "X" ? "O" : "X",
      winner: this.checkWinner(board)
    });

    if (this.state.winner) {
      return;
    }
    
    // Check if board is full (draw)
    if (this.state.board.every((row) => row.every((cell) => cell !== null))) {
      return;
    }

    // Use AI to make a move
    const { object } = await generateObject({
      model: openai("gpt-4o"),
      prompt: `You are playing Tic-tac-toe as player ${player === "X" ? "O" : "X"}. Here's the current board state:

${JSON.stringify(board, null, 2)}

Game rules and context:
- You are playing against ${player}
- Empty cells are null, X's are "X", O's are "O"
- Board positions are [row, col] from 0-2
- You need to respond with a single move as [row, col]
- Winning patterns: 3 in a row horizontally, vertically, or diagonally

Strategic priorities (in order):
1. If you can win in one move, take it
2. If opponent can win in one move, block it
3. If center is open, take it
4. If you can create a fork (two potential winning moves), do it
5. If opponent can create a fork next turn, block it
6. Take a corner if available
7. Take any edge

Analyze the board carefully and make the optimal move following these priorities.
Return only the [row, col] coordinates for your chosen move.`,
      schema: z.object({
        move: z.array(z.number())
      })
    });
    
    await this.makeMove(
      object.move as [number, number],
      player === "X" ? "O" : "X"
    );
  }

  checkWinner(board: TicTacToeState["board"]): Player | null {
    const winningLines = [
      // rows
      [[0, 0], [0, 1], [0, 2]],
      [[1, 0], [1, 1], [1, 2]],
      [[2, 0], [2, 1], [2, 2]],
      // columns
      [[0, 0], [1, 0], [2, 0]],
      [[0, 1], [1, 1], [2, 1]],
      [[0, 2], [1, 2], [2, 2]],
      // diagonals
      [[0, 0], [1, 1], [2, 2]],
      [[0, 2], [1, 1], [2, 0]]
    ];
    
    for (const line of winningLines) {
      const [a, b, c] = line;
      if (
        board[a[0]][a[1]] &&
        board[a[0]][a[1]] === board[b[0]][b[1]] &&
        board[a[0]][a[1]] === board[c[0]][c[1]]
      ) {
        return board[a[0]][a[1]] as Player;
      }
    }
    return null;
  }

  @callable()
  async clearBoard() {
    this.setState(this.initialState);
  }
}

export default {
  async fetch(request: Request, env: Env) {
    return (
      (await routeAgentRequest(request, env)) ||
      new Response("Not found", { status: 404 })
    );
  }
} satisfies ExportedHandler<Env>;

How It Works

1

Player makes move

The client calls agent.stub.makeMove([row, col], "X") when clicking a cell.
2

Validate move

The agent validates the move (correct turn, cell empty) and updates the board state.
3

Check win condition

After each move, checkWinner() scans all possible winning lines (rows, columns, diagonals).
4

AI makes counter-move

If the game isn’t over, the agent calls GPT-4o with:
  • Current board state
  • Strategic priorities (win, block, center, fork, etc.)
  • Request for optimal move coordinates
5

State broadcasts

State changes automatically sync to all connected clients via WebSocket.

AI Strategy

The AI follows a strategic priority list:
  1. Win immediately if possible
  2. Block opponent’s winning move
  3. Take center if open (strongest position)
  4. Create a fork (two ways to win)
  5. Block opponent’s fork
  6. Take a corner (second-best positions)
  7. Take an edge (last resort)
The prompt guides GPT-4o through this decision tree:
prompt: `You are playing Tic-tac-toe as player O.

Strategic priorities (in order):
1. If you can win in one move, take it
2. If opponent can win in one move, block it
3. If center is open, take it
4. If you can create a fork, do it
5. If opponent can create a fork, block it
6. Take a corner if available
7. Take any edge

Analyze the board carefully and make the optimal move.`

Game State

The board is a 3x3 grid stored in agent state:
type TicTacToeState = {
  board: [
    [Played, Played, Played],
    [Played, Played, Played],
    [Played, Played, Played]
  ];
  currentPlayer: "X" | "O";
  winner: "X" | "O" | null;
};
  • null = empty cell
  • "X" = human player
  • "O" = AI opponent

Win Detection

The checkWinner() method scans 8 possible winning lines:
const winningLines = [
  // rows
  [[0, 0], [0, 1], [0, 2]],
  [[1, 0], [1, 1], [1, 2]],
  [[2, 0], [2, 1], [2, 2]],
  // columns
  [[0, 0], [1, 0], [2, 0]],
  [[0, 1], [1, 1], [2, 1]],
  [[0, 2], [1, 2], [2, 2]],
  // diagonals
  [[0, 0], [1, 1], [2, 2]],
  [[0, 2], [1, 1], [2, 0]]
];
Returns the winner ("X" or "O"), or null if the game continues.

Running the Example

1

Install dependencies

npm install
2

Configure OpenAI API key

Copy .env.example to .env:
cp .env.example .env
Add your OpenAI API key:
OPENAI_API_KEY=sk-...
3

Start the server

npm start
4

Play the game

Visit http://localhost:5174 and:
  1. Click any cell to make your move as X
  2. The AI (O) will respond immediately
  3. Continue until someone wins or it’s a draw
  4. Click “New Game” to reset
This example uses GPT-4o which requires an OpenAI API key. The AI makes strategic moves but isn’t unbeatable - try to beat it!

Client Integration

client.tsx
import { useAgent } from "agents/react";
import { useState } from "react";
import type { TicTacToe, TicTacToeState } from "./server";

function Game() {
  const [board, setBoard] = useState<TicTacToeState["board"]>([
    [null, null, null],
    [null, null, null],
    [null, null, null]
  ]);
  const [winner, setWinner] = useState<"X" | "O" | null>(null);

  const agent = useAgent<TicTacToe, TicTacToeState>({
    agent: "TicTacToe",
    onStateUpdate: (state) => {
      setBoard(state.board);
      setWinner(state.winner);
    }
  });

  const handleClick = async (row: number, col: number) => {
    if (board[row][col] || winner) return;
    await agent.stub.makeMove([row, col], "X");
  };

  const handleReset = async () => {
    await agent.stub.clearBoard();
  };

  return (
    <div>
      {winner && <h2>{winner} wins!</h2>}
      <div className="board">
        {board.map((row, i) =>
          row.map((cell, j) => (
            <button key={`${i}-${j}`} onClick={() => handleClick(i, j)}>
              {cell || ""}
            </button>
          ))
        )}
      </div>
      <button onClick={handleReset}>New Game</button>
    </div>
  );
}

Extending This Example

Ideas for enhancements:
  • Difficulty levels - Adjust AI strategy based on difficulty
  • Move history - Store and replay game moves
  • Undo move - Let players take back their last move
  • Multiplayer - Two humans playing over the network
  • Tournament mode - Track wins/losses over multiple games
  • Different AI models - Compare strategies from different LLMs

Counter

Simplest agent with callable methods

AI Chat

Full-featured AI chat with streaming

Workflows

Multi-step workflows with state

Agent API

Full Agent class documentation

Build docs developers (and LLMs) love