Skip to main content

Actions

Most AI frameworks give you one way to communicate with the backend: sendMessage. Your client sends a message, the server responds, and that’s the extent of your API surface. This works for chatbots but constrains everything else. Idyllic takes a different approach. When you decorate a method with @action(), that method becomes a typed RPC endpoint. Your React application calls it as if it were a regular async function. The framework routes the call over WebSocket to your server, executes the method, and returns the result. You define whatever operations your application needs—not just “send message.”
From React, you call these actions through the typed object that useSystem provides:
This design eliminates the ceremony of traditional API development. You don’t define route handlers, write fetch calls, or maintain separate type definitions. The framework extracts types from your method signatures and generates the client interface automatically.

What @action() Does

The decorator transforms a method into an RPC endpoint through several coordinated steps:
  1. Registers the method in a callable registry so the runtime knows which methods can receive external calls
  2. Generates a client stub with a TypeScript signature matching your server method
  3. Sets up RPC handling that routes WebSocket messages to the correct method
  4. Broadcasts state changes to all connected clients after execution completes
  5. Preserves this context so your method can access fields and call other methods
Methods without @action() remain internal, invisible to clients. This lets you organize code with public actions for external requests and private helpers for implementation:

Type Safety

Idyllic extracts types from your action methods and propagates them to client code. The CLI analyzes your system classes, extracts signatures, and writes TypeScript interfaces. Your editor provides autocomplete, and TypeScript catches errors before runtime:
Arguments and return values must be JSON-serializable since they travel over WebSocket. Functions, class instances, and circular references cause runtime errors.

Error Handling

When an action throws, the client’s promise rejects with the error:
For errors clients need to handle programmatically, use ActionError with structured codes:
Error codes let clients branch on failure cases without parsing strings.

Actions and Streaming

Actions return a single value when complete. They don’t stream their return value incrementally. For content that arrives progressively, use a stream<T> field instead. The pattern: an action initiates work and returns metadata; a stream field delivers updates:
Actions perform operations. State reflects current status. Clients call actions to initiate work and observe state for progress.

State Updates

When an action modifies fields, changes persist and sync to all connected clients automatically:
When any client calls these actions, all connected clients receive updated state immediately.

Constraints

Actions use RPC over WebSocket, which imposes constraints:
  • Must be async or return a Promise (RPC is inherently asynchronous)
  • Arguments and returns must be JSON-serializable (no functions, class instances, circular refs)
  • No overloaded signatures (use optional parameters or union types)
  • Only on methods, not getters/setters
  • Only on classes extending AgenticSystem

Best Practices

Keep actions focused. Each action should do one coherent thing. Split compound actions into focused pieces clients can compose. Reset streams at the start. Clear previous content before producing new output:
Return metadata, not content. When content streams to state, return useful metadata instead of duplicating data:

FAQ

Can actions call other actions?

Yes. When an action calls another action within the same system, it’s a direct method call—no RPC, no serialization, no network round-trip:

How do I cancel a running action?

The framework doesn’t provide built-in cancellation. For long-running operations, implement cancellation yourself using a state flag:

Do actions run concurrently?

No. Durable Objects process one request at a time. Simultaneous calls queue and execute sequentially. This prevents race conditions but means long-running actions block other calls.

Next: React

The useSystem hook for typed state and actions