Appearance
Architecture
Uranus follows a serverless, edge-first architecture built entirely on Cloudflare's infrastructure.
System Overview
┌──────────────────────────────────────────────────────────────────┐
│ Cloudflare Edge Network │
├──────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ Static Assets │ │ Worker Entry │ │
│ │ (React SPA) │ │ (src/worker.ts)│ │
│ └────────┬────────┘ └────────┬─────────┘ │
│ │ │ │
│ │ ┌─────────────────┼─────────────────┐ │
│ │ │ │ │ │
│ ▼ ▼ ▼ ▼ │
│ ┌──────────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ API Routes │ │ WebSocket │ │ Queue │ │
│ │ (/api/agents/*) │ │ (/agent/*) │ │ Consumer │ │
│ └────────┬─────────┘ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │ │
│ └────────────────────┼──────────────────┘ │
│ │ │
│ ┌───────────▼───────────┐ │
│ │ Durable Object │ │
│ │ (DashboardAgent) │ │
│ │ │ │
│ │ • WebSocket Handler │ │
│ │ • State Management │ │
│ │ • Alarm Scheduler │ │
│ │ • API Router │ │
│ └───────────┬───────────┘ │
│ │ │
│ ┌───────────────────────┼───────────────────────┐ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ D1 │ │ R2 │ │ Vectorize│ │
│ │ Database │ │ Storage │ │ Embeddings│ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ │
└──────────────────────────────────────────────────────────────────┘Autonomy loop
Beyond serving chat requests, each DashboardAgent participates in a self-maintenance loop driven by three daily crons and a runtime trigger. This is what makes the agent a long-running operator rather than a request handler.
┌────────────────────────────────────┐
│ User input / Gateway event │
└──────────────────┬─────────────────┘
│
┌──────────────────▼─────────────────┐
│ DashboardAgent.runAgenticLoop │
│ │
│ ┌────────────┐ ┌─────────────┐ │
│ │ Tool calls │ ←→ │ Memory L1-3 │ │
│ └────────────┘ └─────────────┘ │
└────────┬─────────────────┬──────────┘
│ │
writes ↓ │ │ reads ↑
▼ │
┌──────────────────────────┴──────┐
│ chat_messages, agent_state, │
│ memory_embeddings, Vectorize │
└──────────────────────────────────┘
▲ ▲
│ │
┌──────────────┴──┐ ┌─────────┴────────────┐
│ Reflection 03:00 │ │ Consolidation 04:00 │
│ glm-4.7-flash │ │ cluster + merge + │
│ writes: │ │ supersede + decay │
│ • observations │ └──────────────────────┘
│ • plan │
│ • memory:reflection:* ┌──────────────────────┐
│ • draft workflows ────►│ workflows (drafts) │
└───────┬───────────┘ └──────────────────────┘
│
▼
┌──────────────────┐ ┌──────────────────────┐
│ Reindex 05:00 │ │ error_spike trigger │
│ embed changed │ │ (>5 failures / 10min)│
│ workflows │ └─────────┬────────────┘
└──────────────────┘ ▼
ad-hoc reflectionEach cron is auto-seeded on the agent's first wake. None require manual setup; the schedules table gets the rows via INSERT OR IGNORE from inside alarm().
See Goals & Reflection, Agent Memory, and Scheduling for details.
Inside one turn
A chat turn through runAgenticLoop touches a handful of small modules around index.ts, each with one job:
| Step | Module | What happens |
|---|---|---|
| Route tools | jev-tools.ts | the Jev decision model picks which tool categories to load; regex triggers are the fallback |
| Build the prompt | index.ts, tasks.ts, compaction.ts | stable blocks (identity, tools, skills) + volatile tail: open tasks, working notes, clock, context budget. History = protected head + overlays + verbatim tail |
| Retrieve | knowledge.ts | if Jev says the message needs documents: fused vector + keyword (+ AI Search) search, scoped to the tenant → agent → contact, reranked |
| Recall | memory.ts | fused memory recall (vector + FTS + exact key) |
| Run tools | turn-checkpoint.ts | after every tool round the turn's progress is checkpointed in DO storage, so an eviction mid-turn resumes instead of re-running |
| After the reply | tasks.ts, memory.ts | Jev auto-closes finished tasks; new memories pass the write-time verifier before they are stored |
Every Jev-backed step has a non-partner fallback and is gated by the data-flow policy, which is itself keyed on the agent's tenant.
Core Components
Worker Entry Point
The main src/worker.ts handles all incoming requests:
typescript
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext) {
// Route handling
// - Static assets (from Vite build)
// - API routes (/api/*)
// - WebSocket connections (/agent/*)
},
async queue(batch: MessageBatch, env: Env) {
// Async workflow execution
}
}Durable Objects
Each agent is a separate DashboardAgent Durable Object instance:
typescript
export class DashboardAgent implements DurableObject {
// Persistent state
private state: DurableObjectState;
// Handle HTTP requests
async fetch(request: Request): Promise<Response>;
// Handle WebSocket messages
async webSocketMessage(ws: WebSocket, message: string);
// Execute scheduled tasks
async alarm(): Promise<void>;
}Key Properties:
- Isolation - Each agent has its own instance
- State - Persistent storage via
this.state.storage - Alarms - Built-in scheduling mechanism
- WebSockets - Native real-time support
Database Layer
D1 (SQLite) stores all persistent data:
| Table | Purpose |
|---|---|
agents | Agent registry |
agent_settings | Per-agent configuration |
workflows | Visual workflow definitions |
schedules | Cron and one-time tasks |
chat_messages | Conversation history |
browser_sessions | Browser automation sessions |
contacts | Contact directory |
data_sources | RAG source files |
Storage Layer
R2 handles file storage:
- Uploaded documents for RAG
- Browser screenshots
- Workflow artifacts
Vector Database
Vectorize enables semantic search:
- Document embeddings
- RAG query matching
- Content similarity search
Request Flow
API Request
Client Request
│
▼
┌─────────────┐
│ Worker │
│ (Routing) │
└──────┬──────┘
│
▼
┌─────────────┐ ┌─────────────┐
│ Pages │────▶│ Durable │
│ Functions │ │ Object │
└─────────────┘ └──────┬──────┘
│
▼
┌─────────────┐
│ D1 │
│ Database │
└─────────────┘WebSocket Connection
Client WebSocket
│
▼
┌─────────────┐
│ Worker │
│ (Upgrade) │
└──────┬──────┘
│
▼
┌─────────────────────────┐
│ Durable Object │
│ │
│ • Accept connection │
│ • Add to client set │
│ • Handle messages │
│ • Broadcast updates │
└─────────────────────────┘Scheduled Task
┌─────────────────────────┐
│ Durable Object │
│ │
│ alarm() triggered │
│ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │ Get pending │ │
│ │ schedules │ │
│ └──────┬──────┘ │
│ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │ Execute │ │
│ │ workflow │ │
│ └──────┬──────┘ │
│ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │ Update next │ │
│ │ run time │ │
│ └─────────────┘ │
└─────────────────────────┘Multi-Tenancy Model
Each agent operates in isolation:
┌─────────────────────────────────────────────┐
│ Cloudflare Workers │
├──────────────┬──────────────┬───────────────┤
│ Agent A │ Agent B │ Agent C │
│ │ │ │
│ ┌──────────┐ │ ┌──────────┐ │ ┌──────────┐ │
│ │ Durable │ │ │ Durable │ │ │ Durable │ │
│ │ Object │ │ │ Object │ │ │ Object │ │
│ └──────────┘ │ └──────────┘ │ └──────────┘ │
│ │ │ │ │ │ │
│ ▼ │ ▼ │ ▼ │
│ ┌───────┐ │ ┌───────┐ │ ┌───────┐ │
│ │State A│ │ │State B│ │ │State C│ │
│ └───────┘ │ └───────┘ │ └───────┘ │
└──────────────┴──────────────┴───────────────┘
│
▼
┌─────────────────────────┐
│ Shared D1 Database │
│ │
│ WHERE agent_id = ? │
└─────────────────────────┘Tenants
Above the agent sits the tenant (tenants table, agents.tenant_id) — the customer that owns one or more agents. The tenant id is whatever the product that manages it uses (for OAC it is the business id), and products keep the registry current through the product-neutral internal platform API; Uranus never reads a product's own database.
Three things key on the tenant:
- Knowledge scope — a search sees this agent's documents plus documents other agents of the same tenant have marked
visibility = business; never another tenant's. - Task memory — tasks carry the business id alongside the conversation and contact.
- Policy —
tenants.partner_ai_consent_atdecides whether customer-facing conversations may use partner models (Jev, Agent Memory).
Security Architecture
Access Control Layers
Cloudflare Access (Optional)
- JWT validation
- Identity provider integration
Email-Based Access
- Role-based permissions (owner, admin, viewer)
- Per-agent access control
Agent Isolation
- Data scoped by agent_id
- Separate Durable Object instances
Authentication Flow
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Client │────▶│ Worker │────▶│ Durable │
│ Request │ │ (Auth) │ │ Object │
└─────────────┘ └──────┬──────┘ └─────────────┘
│
┌────────────┼────────────┐
│ │ │
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ CF │ │ Email │ │ Agent │
│ Access │ │ Check │ │ Access │
└─────────┘ └─────────┘ └─────────┘Technology Choices
| Component | Technology | Rationale |
|---|---|---|
| Runtime | Cloudflare Workers | Edge deployment, low latency |
| State | Durable Objects | Stateful compute, WebSocket support |
| Database | D1 (SQLite) | Edge-native, SQL interface |
| Storage | R2 | S3-compatible, no egress fees |
| Vectors | Vectorize | Native integration, fast similarity search |
| AI | Workers AI | Edge inference, multiple models |
| Frontend | React + Vite | Modern DX, fast builds |
| UI | Radix + Tailwind | Accessible, customizable |
