Skip to content

State Management

Uranus uses a multi-layer state management approach for both frontend and backend.

Overview

State is managed at three levels:

  1. Durable Object State - Persistent backend state
  2. React Context - Frontend application state
  3. Real-time Sync - WebSocket-based synchronization

Backend State

Durable Object Storage

Each agent's Durable Object maintains persistent state:

typescript
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:

typescript
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:

typescript
interface AuthContextType {
  user: User | null
  isAuthenticated: boolean
  isLoading: boolean
  login: (email: string, password: string) => Promise<void>
  logout: () => void
}

Usage:

tsx
const { user, isAuthenticated } = useAuth()

AgentContext

Manages current agent selection:

typescript
interface AgentContextType {
  currentAgent: Agent | null
  agents: Agent[]
  selectAgent: (id: string) => void
  refreshAgents: () => Promise<void>
}

Usage:

tsx
const { currentAgent, selectAgent } = useAgent()

Custom Hooks

useAgent

Fetches and manages agent data:

typescript
const {
  agent,
  isLoading,
  error,
  refresh
} = useAgent(agentId)

useMobile

Detects mobile devices:

typescript
const isMobile = useMobile()

Real-time Synchronization

WebSocket Connection

Clients connect via WebSocket for live updates:

typescript
const ws = new WebSocket(`wss://your-domain.com/agent/${agentId}`)

ws.onmessage = (event) => {
  const update = JSON.parse(event.data)
  handleStateUpdate(update)
}

Message Types

typescript
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:

typescript
// 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:

TableState Type
agentsAgent configuration
agent_settingsAgent settings
workflowsWorkflow definitions
schedulesScheduled tasks
chat_messagesConversation history
contactsContact directory

Query Pattern

All queries are scoped by agent:

sql
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:

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 ────▶ Return

Best 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:

typescript
// 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:

typescript
useEffect(() => {
  const ws = connectWebSocket()

  return () => {
    ws.close()
  }
}, [agentId])

Released under the MIT License.