State Stores in OpenMAIC's lib/store: How Classroom and Agent Data Are Managed
The OpenMAIC codebase uses two distinct Zustand stores located in lib/store to manage classroom and agent data: useStageStore in stage.ts handles classroom lifecycle and content, while useSettingsStore in settings.ts manages global agent configuration including agent selection, mode, and voice overrides.
This guide examines the architecture behind OpenMAIC's state management, focusing specifically on how classroom and agent data flow through the lib/store directory. Understanding these store boundaries is essential for extending the platform or debugging synchronization issues between classroom sessions and agent preferences.
Overview of the State Store Architecture
OpenMAIC implements a clean separation of concerns across two primary stores. Each store owns a distinct domain and exposes typed actions for predictable state mutations.
| Store | Domain | Key File | Primary Responsibility |
|---|---|---|---|
useStageStore |
Classroom (stage) data | lib/store/stage.ts |
Classroom document, scenes, sessions, persistence |
useSettingsStore |
Agent configuration | lib/store/settings.ts |
Agent selection, mode, voice overrides |
This design prevents circular dependencies and ensures that agent preferences persist across classroom sessions while stage-specific data remains scoped to individual classrooms.
useStageStore: Managing Classroom (Stage) Data
The useStageStore defined in [lib/store/stage.ts](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/store/stage.ts) is the core state container for classroom operations. A "stage" in OpenMAIC terminology represents a complete classroom environment with its associated metadata, scenes, and runtime state.
What the Stage Store Holds
- The current stage document (classroom identity and metadata)
- Scene data and the active scene reference
- Chat sessions and generation status
- Viewer-access flags for presentation mode
- UI state including playback/edit mode and toolbar visibility
- Agent roster synchronized from settings (via
generatedAgentConfigs)
Key Actions in useStageStore
| Action | Purpose |
|---|---|
setStage() |
Replace the entire stage object |
setStageAgents() |
Inject agent configurations from settings |
clearStoreForDeletedStage() |
Reset state when a classroom is removed |
loadFromStorage() |
Hydrate store from persistent storage |
| Scene mutations | Add, remove, reorder scenes |
Accessing Classroom Data
import { useStageStore } from '@/lib/store';
// Subscribe to current stage (classroom) object
const currentStage = useStageStore((s) => s.stage);
// Subscribe to specific properties for granular re-renders
const stageTitle = useStageStore((s) => s.stage?.title);
const isGenerating = useStageStore((s) => s.isGenerating);
// Direct state mutation via action
useStageStore.getState().setStage({
...currentStage!,
title: 'Advanced Machine Learning',
});
The store uses Zustand's selector pattern to enable efficient subscriptions. Calls to loadFromStorage(stageId) automatically populate the store and synchronize the agent roster through setStageAgents.
useSettingsStore: Managing Agent Configuration
The useSettingsStore in [lib/store/settings.ts](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/store/settings.ts) serves dual purposes: it contains global application settings and owns the agent configuration domain. This store persists user preferences across sessions and classrooms.
Agent-Related State in useSettingsStore
| State Key | Type | Description |
|---|---|---|
selectedAgentIds |
string[] |
IDs of agents currently selected for use |
agentMode |
'preset' | 'custom' |
Whether using preset agents or custom configuration |
agentVoiceOverrides |
Record<string, VoiceConfig> |
Per-agent voice customizations |
agentSelectionIsUserSet |
boolean |
Flag indicating manual vs. default selection |
Key Actions for Agent Management
| Action | Signature | Purpose |
|---|---|---|
setSelectedAgentIds() |
(ids: string[]) => void |
Update the active agent roster |
setAgentMode() |
(mode: 'preset' | 'custom') => void |
Switch between preset and custom agent modes |
setAgentVoiceOverride() |
(agentId: string, config: VoiceConfig) => void |
Configure voice for a specific agent |
Working with Agent Configuration
import { useSettingsStore } from '@/lib/store';
// Read current agent selection
const selectedAgents = useSettingsStore((s) => s.selectedAgentIds);
const agentMode = useSettingsStore((s) => s.agentMode);
// Update agent selection (e.g., from AgentBar UI)
useSettingsStore.getState().setSelectedAgentIds(['agent-42', 'agent-99']);
// Switch to preset mode
useSettingsStore.getState().setAgentMode('preset');
// Override voice configuration for a specific agent
useSettingsStore.getState().setAgentVoiceOverride('agent-42', {
voiceId: 'en_us_female',
speed: 1.0,
});
The agentSelectionIsUserSet flag enables the UI to distinguish between explicit user choices and system defaults, supporting intelligent reset behavior.
Store Interactions: Loading a Classroom with Agents
When a classroom initializes, the two stores coordinate to ensure consistent state. The useStageStore pulls agent configuration from useSettingsStore during the loading sequence.
import { useStageStore } from '@/lib/store/stage';
import { useSettingsStore } from '@/lib/store/settings';
// Load classroom — this triggers internal synchronization
await useStageStore.getState().loadFromStorage(stageId);
// The stage now contains agent configs derived from settings
const agentsInClassroom = useStageStore
.getState()
.stage?.generatedAgentConfigs ?? [];
// Settings remain the source of truth for user preferences
const userPreferredAgents = useSettingsStore((s) => s.selectedAgentIds);
This pattern ensures that:
- Classroom-specific data (scenes, content) lives in
useStageStore - Cross-cutting concerns (agent preferences) live in
useSettingsStore - Synchronization happens at well-defined lifecycle boundaries
Supporting Files in lib/store
Beyond the two primary stores, the lib/store directory contains additional utilities:
| File | Role |
|---|---|
[lib/store/index.ts](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/store/index.ts) |
Barrel export for all stores (useCanvasStore, useStageStore, useSnapshotStore, useKeyboardStore, useSettingsStore) |
[lib/store/course-refs.ts](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/store/course-refs.ts) |
Utilities for course reference handling, indirectly supporting classroom data relationships |
All stores are implemented with Zustand, a lightweight state management library that provides TypeScript-native type inference and minimal boilerplate compared to alternatives like Redux.
Summary
-
useStageStoreinlib/store/stage.tsmanages classroom lifecycle, scenes, chat sessions, and runtime UI state — it represents a loaded classroom or "stage" -
useSettingsStoreinlib/store/settings.tsholds global configuration including agent selection (selectedAgentIds), agent mode, and voice overrides -
Store coordination occurs through
setStageAgentsandloadFromStorage, ensuring agent preferences propagate into active classrooms -
Clean architectural boundaries prevent state leakage: stage data is ephemeral per-classroom, while settings persist across sessions
-
All stores expose typed actions for predictable mutations and use Zustand's selector pattern for performance
Frequently Asked Questions
How do I access the current classroom data in a React component?
Import useStageStore from @/lib/store and use a selector: const stage = useStageStore((s) => s.stage). This subscribes only to stage changes, preventing unnecessary re-renders. For mutations, access useStageStore.getState().setStage() or other action methods.
What's the difference between selectedAgentIds and generatedAgentConfigs?
selectedAgentIds in useSettingsStore stores the user's agent preferences globally. generatedAgentConfigs on the stage object contains the fully-resolved agent configurations active in a specific classroom. The stage store populates the latter from the former via setStageAgents.
Can I modify agent voice settings per classroom?
Voice overrides are stored globally in useSettingsStore via agentVoiceOverrides. However, the stage store's generatedAgentConfigs captures these at classroom load time. To achieve per-classroom voices, you would need to extend useStageStore with classroom-scoped voice state.
Where does persistence happen for classroom data?
The useStageStore implements persistence through loadFromStorage and implicit save operations. The lib/store/stage.ts file contains the persistence pipeline that serializes stage state, though the exact storage backend (likely IndexedDB or localStorage) would be visible in the full implementation of that module.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →