State Management
This document describes the state management architecture used in the AgentPress monorepo.
Overview
AgentPress uses a layered state management approach:
| Layer | Technology | Purpose |
|---|---|---|
| Global State | Zustand | Application-wide state (user, agents, threads) |
| Server State | React Query | Data fetching, caching, synchronization |
| Component State | React Contexts | Component-level shared state |
| Local State | useState/useRef | Component-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 configurationsetAgents(agents)- Sets agents (supports updater functions)setTools(tools)- Updates available toolsgetTools()- Fetches tools from APIsetActiveThread(thread)- Sets the current chat threadsetAdminThreads(threads)- Updates admin thread listdeleteThread(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 userlogout()- Clears user and sets logout flagresetLogoutFlag()- 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
| Package | Version | Purpose |
|---|---|---|
zustand | ^5.0.0 | Global state management |
@tanstack/react-query | ^5.83.0 | Server state |
@tanstack/react-router | ^1.130.0 | Routing |
ai | ^5.0.59 | AI SDK with streaming |
zod | ^4.1.12 | Runtime validation |