---
title: "State Rendering"
icon: "lucide/Bot"
description: Render the state of your agent with custom UI components.
---

<IframeSwitcher
  id="agent-state-example"
  exampleUrl="https://feature-viewer.copilotkit.ai/mastra/feature/shared_state?sidebar=false&chatDefaultOpen=false"
  codeUrl="https://feature-viewer.copilotkit.ai/mastra/feature/shared_state?view=code&sidebar=false&codeLayout=tabs"
  exampleLabel="Demo"
  codeLabel="Code"
  height="700px"
/>

## What is this?

All Mastra Agents are stateful through working memory. This means that as your agent progresses, working memory is preserved
across the session. CopilotKit allows you to render this state in your application with custom UI components, which we call **Agentic Generative UI**.

## When should I use this?

Rendering the state of your agent in the UI is useful when you want to provide the user with feedback about the overall state of a session. A great example of this
is a situation where a user and an agent are working together to solve a problem. The agent can store a draft in its working memory which is then rendered in the UI.

## Implementation

<Steps>
  <Step>
    ### Run and connect your agent
    <RunAndConnect />
  </Step>
  <Step>
    ### Set up your agent with working memory

    Create your Mastra agent with working memory. Here's a complete example that tracks searches:

    ```ts title="src/mastra/agents/index.ts"

    // Define the agent state schema
    const AgentStateSchema = z.object({
      searches: z.array(
        z.object({
          query: z.string(),
          done: z.boolean(),
        })
      ).default([]),
    });

    export type AgentState = z.infer<typeof AgentStateSchema>;

    // Create tools that update working memory
    const addSearch = createTool({
      id: "addSearch",
      inputSchema: z.object({
        query: z.string(),
      }),
      description: "Add a search to the agent's list of searches",
      execute: async ({ query }) => {
        // Tool implementation - working memory is automatically updated
        return { success: true, query };
      },
    });

    export const searchAgent = new Agent({
      name: "Search Agent",
      model: openai("gpt-5.4"),
      instructions: `
        You are a helpful assistant for storing searches.

        IMPORTANT:
        - Use the addSearch tool to add a search to the agent's state
        - ONLY USE THE addSearch TOOL ONCE FOR A GIVEN QUERY
      `,
      tools: {
        addSearch,
      },
      memory: new Memory({
        storage: new LibSQLStore({ id: "mastra-storage", url: ":memory:" }),
        options: {
          workingMemory: {
            enabled: true,
            schema: AgentStateSchema,
          },
        },
      }),
    });
    ```

  </Step>
  <Step>
    ### Render state of the agent in the chat
    Now we can utilize `useAgent` with a `render` function to render the state of our agent **in the chat**.

    ```tsx title="app/page.tsx"
    // ...
    // ...

    // Define the state of the agent, should match the working memory of your Mastra Agent.
    type AgentState = {
      searches: {
        query: string;
        done: boolean;
      }[];
    };

    function YourMainContent() {
      // ...

      // [!code highlight:13]
      // styles omitted for brevity
      useAgent({
        agentId: "searchAgent",
        render: ({ state }) => (
          <div>
            {state.searches?.map((search, index) => (
              <div key={index}>
                {search.done ? "✅" : "❌"} {search.query}{search.done ? "" : "..."}
              </div>
            ))}
          </div>
        ),
      });

      // ...

      return <div>...</div>;
    }
    ```

    <Callout type="warn" title="Important">
      The `name` parameter must exactly match the agent name you defined in your Mastra instance (e.g., `searchAgent` from above).
    </Callout>

  </Step>
  <Step>
    ### Render state outside of the chat
    You can also render the state of your agent **outside of the chat**. This is useful when you want to render the state of your agent anywhere
    other than the chat.

    ```tsx title="app/page.tsx"
    // ...

    // Define the state of the agent, should match the working memory of your Mastra Agent.
    type AgentState = {
      searches: {
        query: string;
        done: boolean;
      }[];
    };

    function YourMainContent() {
      // ...

      // [!code highlight:3]
      const { agent } = useAgent({
        agentId: "searchAgent",
      })

      // ...

      return (
        <div>
          {/* ... */}
          <div className="flex flex-col gap-2 mt-4">
            {/* [!code highlight:5] */}
            {agent.state?.searches?.map((search, index) => (
              <div key={index} className="flex flex-row">
                {search.done ? "✅" : "❌"} {search.query}
              </div>
            ))}
          </div>
        </div>
      )
    }
    ```

    <Callout type="warn" title="Important">
      The `name` parameter must exactly match the agent name you defined in your Mastra instance (e.g., `searchAgent` from above).
    </Callout>

  </Step>
  <Step>
    ### Give it a try!

    You've now created a component that will render the agent's working memory in the chat.

    <video
      src="https://cdn.copilotkit.ai/docs/copilotkit/images/coagents/agentic-generative-ui.mp4"
      className="rounded-lg shadow-xl"
      loop
      playsInline
      controls
      autoPlay
      muted
    />

  </Step>
</Steps>
