Skip to main content
State lets your agent persist data across conversations, users, and the entire bot. The ADK provides three state scopes, each with different lifetimes and access patterns.

State scopes

ScopeLifetimeAccessDefined in
BotGlobal, shared across all users and conversationsbot.stateagent.config.ts
UserPer-user, persists across all of a user’s conversationsuser.stateagent.config.ts
ConversationPer-conversation, scoped to a single conversationHandler’s state parameterConversation definition

Defining state schemas

Bot and user state

Define bot and user state schemas in agent.config.ts:
import { z, defineConfig } from "@botpress/runtime";

export default defineConfig({
  name: "my-agent",

  bot: {
    state: z.object({
      version: z.number(),
      ticketCounter: z.number(),
    }).default({ version: 0, ticketCounter: 0 }),
  },

  user: {
    state: z.object({
      name: z.string().optional(),
      department: z.string().optional(),
      visitCount: z.number().default(0),
    }),
  },
});
Use .default() on your state schema to set initial values. Without defaults, state fields start as undefined.

Conversation state

Conversation state is defined on each Conversation definition using the state prop:
import { Conversation, z } from "@botpress/runtime";

export default new Conversation({
  channel: "*",
  state: z.object({
    topic: z.string().optional(),
    messageCount: z.number().default(0),
  }),
  handler: async ({ execute, state }) => {
    state.messageCount += 1;

    await execute({
      instructions: `You are a helpful assistant. Messages so far: ${state.messageCount}`,
    });
  },
});

Reading and writing state

Bot state

Import bot from @botpress/runtime to access bot-level state inside any handler, action, tool, or workflow:
import { bot } from "@botpress/runtime";

// Read
const counter = bot.state.ticketCounter;

// Write
bot.state.ticketCounter = counter + 1;

User state

Import user from @botpress/runtime to access per-user state inside any handler, action, tool, or workflow:
import { user } from "@botpress/runtime";

// Read
const name = user.state.name;

// Write
user.state.visitCount = (user.state.visitCount || 0) + 1;

Conversation state

Conversation state is accessed via the state parameter in the handler:
export default new Conversation({
  channel: "*",
  state: z.object({
    phase: z.string().default("greeting"),
  }),
  handler: async ({ state, execute }) => {
    if (state.phase === "greeting") {
      state.phase = "main";
    }

    await execute({
      instructions: `Current phase: ${state.phase}`,
    });
  },
});
State changes are automatically tracked and persisted. You don’t need to call a save method: just mutate the state object directly.

Tags

Tags are string key-value pairs you can attach to bots, users, conversations, messages, and workflows. They’re useful for categorization, filtering, and querying.

Defining tags

Define tag schemas in agent.config.ts:
export default defineConfig({
  name: "my-agent",

  bot: {
    tags: {
      environment: { title: "Environment", description: "Current deployment environment" },
    },
  },

  user: {
    tags: {
      role: { title: "Role", description: "User's role in the organization" },
    },
  },

  conversation: {
    tags: {
      priority: { title: "Priority", description: "Conversation priority level" },
    },
  },
});

Reading and writing tags

import { bot, user } from "@botpress/runtime";

// Bot tags
bot.tags.environment = "production";
const env = bot.tags.environment;

// User tags
user.tags.role = "admin";
Conversation tags are accessed via the conversation instance in handlers:
export default new Conversation({
  channel: "*",
  handler: async ({ conversation, execute }) => {
    // Read
    const priority = conversation.tags.priority;

    // Write
    conversation.tags.priority = "high";
  },
});
System tags (set by integrations, containing : in the key) are read-only. You can read them but any writes are silently ignored.

State references

You can store references to workflow instances in state. They are automatically serialized when saved and loaded back as full instances when read:
import { Conversation, Reference, z } from "@botpress/runtime";
import OnboardingWorkflow from "../workflows/onboarding";

export default new Conversation({
  channel: "*",
  state: z.object({
    activeWorkflow: Reference.Workflow("onboarding").optional(),
  }),
  handler: async ({ state }) => {
    // Start a workflow and store the reference in state
    if (!state.activeWorkflow) {
      state.activeWorkflow = await OnboardingWorkflow.start({});
    }
  },
});

Bot and user identity

The bot and user objects also expose an id property:
import { bot, user } from "@botpress/runtime";

const botId = bot.id;
const userId = user.id;
Last modified on April 6, 2026