AgenticSystem
In Idyllic, your backend logic lives in a TypeScript class that extendsAgenticSystem. You define your application’s state as class properties decorated with @field and implement its behavior as methods decorated with @action(). The Idyllic runtime takes care of persisting your state, streaming updates to connected clients, and keeping everything synchronized across connections.
This design means you write straightforward object-oriented code rather than wiring together API routes, database queries, and WebSocket handlers. The framework handles the infrastructure concerns so you can focus on your application logic.
The System is the Actor
EachAgenticSystem class runs as a Cloudflare Durable Object—a single-threaded, stateful actor that persists indefinitely. This is the unit of isolation and persistence in Idyllic. When you deploy your system, you’re deploying an actor that can be instantiated, addressed by ID, and will maintain its state across restarts and deployments.
Within your system, you may have multiple “agents”—researcher objects, writer objects, employee objects. But these agents are not separate actors. They are properties of your class, sharing the same execution context and memory. The coordination between agents happens through regular method calls, not through message passing between isolated processes.
This distinction matters for how you think about your code:
- The Durable Object is the actor. It has an ID, maintains state, handles requests one at a time.
- Agents are objects inside the actor. They share state, can call each other’s methods, coordinate through code.
- Coordination is plain TypeScript. Sequential calls, conditional branches, loops—no special primitives needed.
Class Structure
TheAgenticSystem base class provides properties for accessing instance metadata and connections, lifecycle hooks for responding to events, and built-in methods for communicating with clients and scheduling work.
Properties
id
Every system instance receives a unique identifier when it’s created. This ID remains stable across server restarts, hibernation cycles, and deployments, making it suitable for use in URLs, database references, or anywhere you need to identify a specific instance.
name
The name property contains the system type name, which Idyllic derives from your class name or filename. This is useful for logging or building generic utilities that work with multiple system types.
connections
The connections array contains all clients currently connected to this system instance via WebSocket. You can iterate over it to access connection metadata or count active users.
Defining State
State properties are marked with the@field decorator. Idyllic tracks changes to these properties and handles persistence and synchronization automatically.
@field property, Idyllic detects the change and immediately synchronizes it to all connected clients. There’s no explicit save or publish step required.
Streaming Values
Some values need to update incrementally rather than all at once. When streaming a response from a language model, you want clients to see each chunk as it arrives rather than waiting for the complete response. Wrapping a property withstream<T>() enables this incremental update pattern.
append() adds content incrementally (strings concatenate, objects merge), set() replaces the entire value, and complete() signals that streaming is finished. Clients receive only the incremental changes rather than the full value on each update.
Supported Types
Idyllic can persist and synchronize most JavaScript data types. Primitives (strings, numbers, booleans, null), plain objects, and arrays work as expected. The framework also supportsMap and Set by serializing them as entries and arrays.
Functions and class instances cannot be serialized and are not supported. Streaming values synchronize in real-time, though only the current value persists across restarts.
Nested State
State can be arbitrarily nested. Idyllic tracks changes at any depth and synchronizes them appropriately.Lifecycle Methods
Override these methods in your subclass to respond to events in your system’s lifecycle.onCreate()
Called once when an instance is first created—not when it wakes from hibernation or restarts after deployment. Use it for one-time initialization: setting initial state that depends on external data, loading configuration, or setting up integrations.
onConnect(connection)
Called each time a client establishes a WebSocket connection. Use it to track active users, send welcome messages, or notify other clients.
connection object provides a unique id and any custom metadata the client passed when connecting.
onDisconnect(connection)
Called when a client’s WebSocket connection closes, whether from navigation, network interruption, or explicit disconnect.
onDestroy()
Called when an instance is being permanently deleted. Use it to clean up external resources or cancel subscriptions.
Built-in Methods
broadcast(message)
Send a message to every client currently connected to this instance. Use this for notifications or events that all clients should receive.
broadcastTo(connectionIds, message)
Send a message to specific clients by their connection IDs. Useful for targeted notifications like admin-only alerts.
schedule(when, method, payload?)
Schedule a method to run at a future time. You can specify timing as milliseconds (delay), a Date object, or a cron expression for recurring execution.
cancelSchedule(schedule)
Cancel a previously scheduled execution before it runs.
sql
For advanced use cases, you can execute raw SQL queries against the instance’s SQLite database. This is an escape hatch for complex queries or migrations. Use sparingly—the @field system handles persistence automatically for most applications.
Persistence
Idyllic automatically persists@field properties to SQLite storage. You don’t write database code, define schemas, or call save methods. When you modify a field, the change persists.
This persistence survives server restarts, hibernation, deployments, and infrastructure failures. Cloudflare replicates data automatically.
What Persists
Everything marked with@field that can be serialized to JSON persists automatically.
What Doesn’t Persist
Properties without@field do not persist. They reset to initial values when the instance restarts or wakes from hibernation. Use these for caches or temporary computation.
Persistence Guarantees
Idyllic provides strong guarantees: all state changes within a method succeed or fail together (transactional), reads always reflect recent writes (consistency), changes survive failures after method completion (durability), and only one method executes at a time (no race conditions).Environment Variables
Access secrets and configuration throughthis.env. Idyllic injects environment variables automatically.
.dev.vars (gitignored by default):
FAQ
What happens when I redeploy?
State persists across deployments. Your new code runs against existing state. If you change state structure, handle migration at the start of methods:Can I have multiple system types?
Yes. Each file in yoursystems/ directory that exports a default class becomes a system type. Clients specify which type to connect to.
Can methods run concurrently?
No. Each Durable Object processes one request at a time, preventing race conditions. Long-running methods block other calls. UsePromise.all within a method for parallel async operations, or delegate to separate system instances.
How do I share state between instances?
Each instance has isolated state. For shared data: create a “coordinator” system that holds shared state, use external storage like a database, or have instances call methods on each other using their IDs.Next: Streaming
The stream<T> primitive for real-time updates