State Management

This document describes the state management architecture used in the AgentPress monorepo.

Overview

AgentPress uses a layered state management approach:

LayerTechnologyPurpose
Global StateZustandApplication-wide state (user, agents, threads)
Server StateReact QueryData fetching, caching, synchronization
Component StateReact ContextsComponent-level shared state
Local StateuseState/useRefComponent-specific state

Zustand Stores

All stores are located in packages/lib/src/stores/.

Chat Store

File: packages/lib/src/stores/chatStore.ts

Manages agent configuration, tools, and chat threads.

interface ChatStore {
  agents: TAgentsConfig; // Array of agent configurations
  tools: ITool[]; // Available tools for agents
  activeThread: IThread | undefined; // Currently active chat thread
  adminThreads: IThread[]; // All threads visible to admin
}

Actions:

  • updateAgent(agent) - Updates a single agent configuration
  • setAgents(agents) - Sets agents (supports updater functions)
  • setTools(tools) - Updates available tools
  • getTools() - Fetches tools from API
  • setActiveThread(thread) - Sets the current chat thread
  • setAdminThreads(threads) - Updates admin thread list
  • deleteThread(threadId) - Removes thread from admin list

Usage:

import { useChatStore } from "@agentpress/lib/stores";

function Component() {
  const { agents, activeThread, setActiveThread } = useChatStore();

  // Select specific slice
  const agents = useChatStore((state) => state.agents);
}

User Store

File: packages/lib/src/stores/userStore.ts

Manages authentication state and user session.

interface UserStore {
  user: IUser | undefined; // Current user object
  hasLoggedOut: boolean; // Flag for logout tracking
}

Actions:

  • setUser(user) - Sets the authenticated user
  • logout() - Clears user and sets logout flag
  • resetLogoutFlag() - Resets the logout flag on re-login

RAG Store

File: packages/lib/src/stores/ragStore.ts

Manages knowledge bases and RAG resources.

interface RagStore {
  resources: Record<string, IResource[]>; // Resources by store name
  resourceGroups: IResourceGroupServer[]; // Knowledge base groups
}

Task WebSocket Store

File: packages/lib/src/stores/taskWebSocketStore.ts

Manages task execution progress via WebSocket connections.

interface TaskWebSocketStore {
  activeTasks: TaskProgress[];
  allTasks: TaskProgress[];
  isConnected: boolean;
  finishedTaskNotificationQueue: Record<string, any>;
}

React Query (Server State)

Used for server state synchronization and caching.

Query Pattern

import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";

function Component() {
  const queryClient = useQueryClient();

  // Query with conditional execution
  const { data, isLoading, error } = useQuery({
    queryKey: ["embeddingModel"],
    queryFn: apiGetEmbeddingModel,
    enabled: !!user, // Only fetch when user exists
  });

  // Mutation with cache invalidation
  const mutation = useMutation({
    mutationFn: apiUpdateEmbeddingModel,
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ["embeddingModel"] });
      toast.success("Updated successfully");
    },
    onError: (error) => {
      toast.error(`Failed: ${error.message}`);
    },
  });
}

Common Query Keys

  • ["embeddingModel"] - Embedding model config
  • ["resourceGroups"] - Knowledge bases
  • ["evaluations"] - Evaluation definitions
  • ["agents"] - Agent configurations
  • ["threads"] - Conversation threads

React Context

Limited usage for component-level state sharing.

AdminHeaderContext

File: packages/ui/src/contexts/AdminHeaderContext.tsx

Manages dynamic page header content for admin pages.

interface PageHeaderActions {
  leftActions?: React.ReactNode;
  rightActions?: React.ReactNode;
  customTitle?: React.ReactNode;
}

// Usage
import { useAdminHeader } from "@agentpress/ui/contexts";

function AdminPage() {
  const { setPageActions } = useAdminHeader();

  useEffect(() => {
    setPageActions({
      rightActions: <Button>Create</Button>,
    });
    return () => setPageActions(null);
  }, []);
}

API Request Pattern

File: packages/lib/src/backend/requests.ts

API functions directly update Zustand stores (side-effect-driven pattern).

export const apiGetTools = async () => {
  const { setTools } = useChatStore.getState();
  try {
    const res = await fetch(`${getAgentPressConfig().apiHost}/chat/tools`);
    const data = await res.json();
    setTools(data); // Direct store update
  } catch (e) {
    throw new Error(`Failed to fetch tools: ${e}`);
  }
};

Key Characteristics:

  • Direct store access via .getState() (not hooks)
  • Side effects within request functions
  • Async/await pattern with error propagation

Provider Architecture

AgentPressProvider

File: packages/ui/src/core/providers/AgentPressProvider.tsx

export function AgentPressProvider({ children, queryClient }: Props) {
  const [queryClient] = useState(
    () => customQueryClient || new QueryClient()
  );

  // Initialize WebSocket hook - populates Zustand store
  useTaskWebSocket();

  return (
    <AgentPressErrorBoundary>
      <QueryClientProvider client={queryClient}>
        <ThemeProvider attribute="class" enableSystem>
          <TooltipProvider delayDuration={0}>
            {children}
          </TooltipProvider>
        </ThemeProvider>
      </QueryClientProvider>
    </AgentPressErrorBoundary>
  );
}

Configuration Management

File: packages/lib/src/config.ts

Singleton configuration for library settings.

export interface LibConfig {
  apiHost: string;
  toolsUI: IToolsUI;
  isGuest?: boolean;
  appType: EAppType;
  voiceTranscription?: boolean;
}

export function setAgentPressConfig(config: Partial<LibConfig>): void;
export function getAgentPressConfig(): LibConfig;

Important: Must be called in routes.tsx BEFORE app initialization.

Best Practices

1. Separation of Concerns

  • Global state in Zustand stores
  • Server state in React Query
  • Component state with hooks
  • API calls in dedicated requests module

2. Immutable Updates

// Zustand enforces immutability
set((state) => ({
  agents: state.agents.map((a) => (a.id === id ? { ...a, ...updates } : a)),
}));

3. Performance Optimization

// Select specific slices to prevent unnecessary re-renders
const agents = useChatStore((state) => state.agents);

// Memoize expensive computations
const filtered = useMemo(() => agents.filter((a) => a.enabled), [agents]);

4. Error Handling

// Toast notifications for errors
onError: (error) => {
  toast.error(`Failed: ${error.message}`);
}

// Error boundaries for React rendering
<AgentPressErrorBoundary>
  {children}
</AgentPressErrorBoundary>

Dependencies

PackageVersionPurpose
zustand^5.0.0Global state management
@tanstack/react-query^5.83.0Server state
@tanstack/react-router^1.130.0Routing
ai^5.0.59AI SDK with streaming
zod^4.1.12Runtime validation

On this page