Skip to Content
ExamplesWorkflowsMulti-turn Human in the Loop

Multi-Turn Human in the Loop

Multi-turn human-in-the-loop workflows enable ongoing interaction between humans and AI agents, allowing for complex decision-making processes that require multiple rounds of input and response. These workflows can suspend execution at specific points, wait for human input, and continue processing based on the responses received.

In this example, the multi-turn workflow is used to create a Heads Up game that demonstrates how to create interactive workflows using suspend/resume functionality and conditional logic with dountil to repeat a workflow step until a specific condition is met.

This example consists of three main components:

  1. A Famous Person Agent that generates a famous person’s name.
  2. A Game Agent that handles the gameplay.
  3. A Multi-Turn Workflow that orchestrates the interaction.

Prerequisites

This example uses the openai model. Make sure to add the following to your .env file:

.env
OPENAI_API_KEY=<your-api-key>

Famous person agent

The famousPersonAgent generates a unique name each time the game is played, using semantic memory to avoid repeating suggestions.

src/mastra/agents/example-famous-person-agent.ts
import { openai } from "@ai-sdk/openai"; import { Agent } from "@mastra/core/agent"; import { Memory } from "@mastra/memory"; import { LibSQLVector } from "@mastra/libsql"; export const famousPersonAgent = new Agent({ name: "Famous Person Generator", instructions: `You are a famous person generator for a "Heads Up" guessing game. Generate the name of a well-known famous person who: - Is recognizable to most people - Has distinctive characteristics that can be described with yes/no questions - Is appropriate for all audiences - Has a clear, unambiguous name IMPORTANT: Use your memory to check what famous people you've already suggested and NEVER repeat a person you've already suggested. Examples: Albert Einstein, Beyoncé, Leonardo da Vinci, Oprah Winfrey, Michael Jordan Return only the person's name, nothing else.`, model: openai("gpt-4o"), memory: new Memory({ vector: new LibSQLVector({ connectionUrl: "file:../mastra.db" }), embedder: openai.embedding("text-embedding-3-small"), options: { lastMessages: 5, semanticRecall: { topK: 10, messageRange: 1 } } }) });

See Agent for a full list of configuration options.

Game agent

The gameAgent handles user interactions by responding to questions and validating guesses.

src/mastra/agents/example-game-agent.ts
import { openai } from "@ai-sdk/openai"; import { Agent } from "@mastra/core/agent"; export const gameAgent = new Agent({ name: "Game Agent", instructions: `You are a helpful game assistant for a "Heads Up" guessing game. CRITICAL: You know the famous person's name but you must NEVER reveal it in any response. When a user asks a question about the famous person: - Answer truthfully based on the famous person provided - Keep responses concise and friendly - NEVER mention the person's name, even if it seems natural - NEVER reveal gender, nationality, or other characteristics unless specifically asked about them - Answer yes/no questions with clear "Yes" or "No" responses - Be consistent - same question asked differently should get the same answer - Ask for clarification if a question is unclear - If multiple questions are asked at once, ask them to ask one at a time When they make a guess: - If correct: Congratulate them warmly - If incorrect: Politely correct them and encourage them to try again Encourage players to make a guess when they seem to have enough information. You must return a JSON object with: - response: Your response to the user - gameWon: true if they guessed correctly, false otherwise`, model: openai("gpt-4o") });

Multi-turn workflow

The workflow coordinates the full interaction using suspend/resume to pause for human input and dountil to repeat the game loop until a condition is met.

The startStep generates a name using the famousPersonAgent, while the gameStep runs the interaction through the gameAgent, which handles both questions and guesses and produces structured output that includes a gameWon boolean.

src/mastra/workflows/example-heads-up-workflow.ts
import { createWorkflow, createStep } from "@mastra/core/workflows"; import { z } from "zod"; const startStep = createStep({ id: "start-step", description: "Get the name of a famous person", inputSchema: z.object({ start: z.boolean() }), outputSchema: z.object({ famousPerson: z.string(), guessCount: z.number() }), execute: async ({ mastra }) => { const agent = mastra.getAgent("famousPersonAgent"); const response = await agent.generate("Generate a famous person's name", { temperature: 1.2, topP: 0.9, memory: { resource: "heads-up-game", thread: "famous-person-generator" } }); const famousPerson = response.text.trim(); return { famousPerson, guessCount: 0 }; } }); const gameStep = createStep({ id: "game-step", description: "Handles the question-answer-continue loop", inputSchema: z.object({ famousPerson: z.string(), guessCount: z.number() }), resumeSchema: z.object({ userMessage: z.string() }), suspendSchema: z.object({ suspendResponse: z.string() }), outputSchema: z.object({ famousPerson: z.string(), gameWon: z.boolean(), agentResponse: z.string(), guessCount: z.number() }), execute: async ({ inputData, mastra, resumeData, suspend }) => { let { famousPerson, guessCount } = inputData; const { userMessage } = resumeData ?? {}; if (!userMessage) { await suspend({ suspendResponse: "I'm thinking of a famous person. Ask me yes/no questions to figure out who it is!" }); } const agent = mastra.getAgent("gameAgent"); const response = await agent.generate( ` The famous person is: ${famousPerson} The user said: "${userMessage}" Please respond appropriately. If this is a guess, tell me if it's correct. `, { output: z.object({ response: z.string(), gameWon: z.boolean() }) } ); const { response: agentResponse, gameWon } = response.object; guessCount++; return { famousPerson, gameWon, agentResponse, guessCount }; } }); const winStep = createStep({ id: "win-step", description: "Handle game win logic", inputSchema: z.object({ famousPerson: z.string(), gameWon: z.boolean(), agentResponse: z.string(), guessCount: z.number() }), outputSchema: z.object({ famousPerson: z.string(), gameWon: z.boolean(), guessCount: z.number() }), execute: async ({ inputData }) => { const { famousPerson, gameWon, guessCount } = inputData; return { famousPerson, gameWon, guessCount }; } }); export const headsUpWorkflow = createWorkflow({ id: "heads-up-workflow", inputSchema: z.object({ start: z.boolean() }), outputSchema: z.object({ famousPerson: z.string(), gameWon: z.boolean(), guessCount: z.number() }) }) .then(startStep) .dountil(gameStep, async ({ inputData: { gameWon } }) => gameWon) .then(winStep) .commit();

See Workflow for a full list of configuration options.

Registering the agents and workflow

To use a workflow or an agent, register them in your main Mastra instance.

src/mastra/index.ts
import { Mastra } from "@mastra/core/mastra"; import { headsUpWorkflow } from "./workflows/example-heads-up-workflow"; import { famousPersonAgent } from "./agents/example-famous-person-agent"; import { gameAgent } from "./agents/example-game-agent"; export const mastra = new Mastra({ workflows: { headsUpWorkflow }, agents: { famousPersonAgent, gameAgent } });
View Example on GitHub