State Management
Uranus uses a multi-layer state management approach for both frontend and backend.
Overview
State is managed at three levels:
- Durable Object State - Persistent backend state
- React Context - Frontend application state
- Real-time Sync - WebSocket-based synchronization
Backend State
Durable Object Storage
Each agent's Durable Object maintains persistent state:
interface AgentState {
config: {
name: string
description: string
model: string
temperature: number
}
connectedClients: number
mcpServers: MCPServer[]
pendingSchedules: Schedule[]
}State Updates
State changes are tracked with timestamps:
interface StateUpdate {
type: 'client' | 'server' | 'sync'
timestamp: number
changes: Partial<AgentState>
}Persistence
Durable Object state persists across:
- Worker restarts
- Geographic migrations
- Connection drops
Frontend State
React Contexts
AuthContext
Manages authentication state:
interface AuthContextType {
user: User | null
isAuthenticated: boolean
isLoading: boolean
login: (email: string, password: string) => Promise<void>
logout: () => void
}Usage:
const { user, isAuthenticated } = useAuth()AgentContext
Manages current agent selection:
interface AgentContextType {
currentAgent: Agent | null
agents: Agent[]
selectAgent: (id: string) => void
refreshAgents: () => Promise<void>
}Usage:
const { currentAgent, selectAgent } = useAgent()Custom Hooks
useAgent
Fetches and manages agent data:
const {
agent,
isLoading,
error,
refresh
} = useAgent(agentId)useMobile
Detects mobile devices:
const isMobile = useMobile()Real-time Synchronization
WebSocket Connection
Clients connect via WebSocket for live updates:
const ws = new WebSocket(`wss://your-domain.com/agent/${agentId}`)
ws.onmessage = (event) => {
const update = JSON.parse(event.data)
handleStateUpdate(update)
}Message Types
type WebSocketMessage =
| { type: 'state:update', data: Partial<AgentState> }
| { type: 'client:connected', data: { count: number } }
| { type: 'schedule:executed', data: { id: string, result: any } }
| { type: 'workflow:status', data: { id: string, status: string } }Broadcast Updates
The Durable Object broadcasts to all connected clients:
// In DashboardAgent
broadcastState(update: StateUpdate) {
for (const client of this.clients) {
client.send(JSON.stringify({
type: 'state:update',
data: update
}))
}
}State Flow
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Client A │ │ Client B │ │ Client C │
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘
│ │ │
│ WebSocket │ WebSocket │ WebSocket
│ │ │
└───────────────────┼───────────────────┘
│
▼
┌─────────────────────────┐
│ Durable Object │
│ │
│ ┌──────────────────┐ │
│ │ State Store │ │
│ └────────┬─────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────┐ │
│ │ Broadcast │ │
│ │ to Clients │ │
│ └──────────────────┘ │
└─────────────────────────┘Database State
D1 Tables
Persistent data is stored in D1:
| Table | State Type |
|---|---|
agents | Agent configuration |
agent_settings | Agent settings |
workflows | Workflow definitions |
schedules | Scheduled tasks |
chat_messages | Conversation history |
contacts | Contact directory |
Query Pattern
All queries are scoped by agent:
SELECT * FROM workflows WHERE agent_id = ?State Viewing
Dashboard State Page
The State page (/state) shows:
- Current agent configuration
- Connected client count
- Active MCP servers
- Pending schedules
- Real-time updates
JSON View
View raw state as JSON:
{
"config": {
"name": "support-bot",
"description": "Customer support agent",
"model": "@cf/meta/llama-3.1-8b-instruct",
"temperature": 0.7
},
"connectedClients": 3,
"mcpServers": [],
"pendingSchedules": [
{
"id": "schedule-1",
"name": "Daily Report",
"next_run": "2024-12-16T09:00:00Z"
}
]
}State Persistence Strategy
Immediate Persistence
Critical state is persisted immediately:
- Configuration changes
- Schedule updates
- Workflow modifications
Batched Persistence
High-frequency state is batched:
- Metrics snapshots
- Log entries
- Connection counts
Cache Strategy
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Request │────▶│ Cache │────▶│ Database │
│ │ │ (Memory) │ │ (D1) │
└─────────────┘ └─────────────┘ └─────────────┘
│
▼
Cache Hit? ────▶ Return cached
│
▼ No
Query D1 ────▶ Update cache ────▶ ReturnBest Practices
1. Minimize State
Keep state lean:
- Store only necessary data
- Compute derived values on-demand
- Clean up stale state
2. Use Optimistic Updates
Update UI immediately, sync later:
// Update local state first
setWorkflows([...workflows, newWorkflow])
// Then sync to server
await api.createWorkflow(newWorkflow)3. Handle Conflicts
Resolve state conflicts gracefully:
- Last-write-wins for simple values
- Merge strategies for complex objects
- User notification for conflicts
4. Monitor State Size
Keep Durable Object state reasonable:
- Large data goes to D1
- Files go to R2
- State holds references
5. Clean Up Connections
Handle WebSocket lifecycle:
useEffect(() => {
const ws = connectWebSocket()
return () => {
ws.close()
}
}, [agentId])