Skip to main content

Idyllic

Most AI frameworks assume you’re building a chatbot. Idyllic assumes you’re building something more interesting. Your backend is a TypeScript class. Properties sync to clients automatically. Methods become your API. Streaming is a type annotation, not infrastructure you build. You write a class, deploy with one command, and watch your AI system work in real time.
import { AgenticSystem, field, action, stream } from 'idyllic';

export default class Counter extends AgenticSystem {
  @field count = 0;
  @field message = stream<string>('');

  @action()
  async increment() {
    this.count++;
    for await (const word of ai.stream('Say something about ' + this.count)) {
      this.message.append(word);
    }
    this.message.complete();
  }
}
const { count, message, increment } = useSystem<Counter>();

<button onClick={increment}>{count}</button>
<div>{message.current}</div>
That’s a complete application. The @field decorator marks state that syncs to all connected clients. The @action() decorator exposes a method as callable from React. The stream<string> type creates a field that updates incrementally as AI generates output. Open two browser tabs and click the button—both tabs update instantly.

What Makes This Different

Not chat. Most AI tools give you useChat and a messages array. That’s fine for chatbots, but a document editor has documents, a research tool has sources and findings, a dashboard has queries and results. Idyllic lets you define whatever state shape your application needs. No infrastructure code. When you call this.message.append(chunk), the framework broadcasts that chunk to every connected client over WebSocket. When you assign to this.count, that change persists to durable storage and syncs to all clients. You don’t write SSE parsers, WebSocket handlers, or database queries. Type safety across the network. The useSystem<Counter>() hook knows that count is a number and increment takes no arguments because it reads your class definition. Change a method signature on the server and TypeScript shows errors in your React code. One command to production. npx idyllic deploy bundles your code and ships it to Cloudflare’s edge network. Your system runs in 300+ locations worldwide with ~10ms cold starts. State persists automatically. No containers, no database config.

Agents as Properties

The real power appears when you build multi-agent systems. In Idyllic, agents aren’t separate processes that send messages to each other—they’re objects that live inside your class. Coordination is just code.
export default class VirtualOffice extends AgenticSystem {
  @field alice = { role: 'researcher', output: stream<string>('') };
  @field bob = { role: 'writer', output: stream<string>('') };

  @action()
  async createArticle(topic: string) {
    // Alice researches
    const research = await this.research(this.alice, topic);

    // Bob writes based on her findings
    await this.write(this.bob, research);
  }

  private async research(agent: typeof this.alice, topic: string) {
    for await (const chunk of ai.stream(`Research ${topic}`)) {
      agent.output.append(chunk);
    }
    agent.output.complete();
    return agent.output.current;
  }

  private async write(agent: typeof this.bob, research: string) {
    for await (const chunk of ai.stream(`Write article from: ${research}`)) {
      agent.output.append(chunk);
    }
    agent.output.complete();
  }
}
Both agents stream their output simultaneously. Clients see Alice’s research and Bob’s draft filling in at the same time. Sequential calls, parallel calls, conditionals, loops—you express coordination in TypeScript, not a graph DSL.

Autonomous Systems

Systems can schedule their own future execution, even when no one is watching.
@action()
async startMonitoring(topic: string) {
  this.topic = topic;
  await this.check();
  this.schedule('every 1 hour', 'check');
}

async check() {
  const news = await this.searchNews(this.topic);
  if (news.length > 0) {
    this.findings.push(...news);
  }
}
Between scheduled runs, the system hibernates and costs nothing. When the hour passes, it wakes, runs check(), and hibernates again. A user can close their browser, come back days later, and find accumulated findings waiting.

Core Primitives

PrimitivePurpose
@fieldMarks a property as synced to clients and persisted
@action()Marks a method as callable from React
stream<T>()Creates a field that supports incremental updates
useSystem<T>()React hook that connects to a system instance
schedule()Runs a method at a future time or on a recurring basis
These five concepts cover most of what you need. The framework handles WebSocket connections, state synchronization, persistence, and deployment.

When to Use Idyllic

Idyllic works well for AI applications where:
  • You need real-time streaming to multiple clients
  • State should persist across sessions
  • The data model isn’t just chat messages
  • You want agents that work autonomously
  • You’d rather write TypeScript than configure infrastructure
It’s less suited for:
  • Simple chatbots where useChat is enough
  • Applications that need to run outside Cloudflare
  • Systems that require distributed agents across machines

Next Steps