Skip to content

Agents SDK

Give each agent an identity, durable state, connections, and a lifecycle.

  • Libraries Runtime.Agents
  • npm agents 0.22.0
  • Upstream/API Lifecycle API experimental

Agent identity and state

An agent instance has a name and Durable Object identity. Choose that identity around the unit that owns the work: a conversation, project, or team. The examples below use the SDK's lifecycle integration with an F# Durable Object.

Connection state, application tables, and scheduling belong to the agent. Files it edits can live in a workspace; code it runs can execute in a Sandbox. Those resources have their own persistence and lifecycle rules.

agents/lifecycle is experimental in the pinned package. The examples compile against that version; consult the Agents SDK documentation when changing versions.

Game Lobby

Players join the lobby over WebSockets. Lifecycle.install adds the Agents SDK's connection handling to a plain Durable Object, and broadcast sends each message to every player except the sender.

open Fable.Core

module Workers = FSharp.CloudEdge.Runtime.Workers
module Runtime = FSharp.CloudEdge.Runtime.Workers.Cloudflare.Workers
module Agents = FSharp.CloudEdge.Runtime.Agents
module Lifecycle = FSharp.CloudEdge.Runtime.Agents.Lifecycle

[<AttachMembers>]
type GameLobby(ctx: Workers.DurableObjectState<obj>, env: obj) as this =
    inherit Runtime.DurableObject<obj, obj>(ctx, env)

    let lifecycle = Lifecycle.Lifecycle.install<obj, obj> this

    member _.onConnect(player: Agents.Connection<obj>, _context: Agents.ConnectionContext) =
        player.send (U3.Case1 $"Welcome to {lifecycle.name}")

    member _.onMessage(player: Agents.Connection<obj>, message: Agents.WSMessage) =
        lifecycle.broadcast (message, [| player.id |])

Needs a Durable Object binding for GameLobby and a Worker that forwards each connection to a lobby by name, as Room Lookup on the Durable Objects page does. Worker Upload shows how to declare the binding.

Emitted JavaScript
import { concat } from "./fable_modules/fable-library-js.5.13.0/String.js";
import { defaultOf } from "./fable_modules/fable-library-js.5.13.0/Util.js";
import { FSharpRef } from "./fable_modules/fable-library-js.5.13.0/Types.js";
import { Lifecycle } from "agents/lifecycle";
import { DurableObject } from "cloudflare:workers";
import { class_type, obj_type } from "./fable_modules/fable-library-js.5.13.0/Reflection.js";

export class GameLobby extends DurableObject {
    constructor(ctx, env) {
        super(ctx, env);
        const this$ = new FSharpRef(defaultOf());
        this$.contents = this;
        this.lifecycle = Lifecycle.install(this$.contents);
        this["init@11"] = 1;
    }
    onConnect(player, _context) {
        const _ = this;
        player.send(concat("Welcome to ", _.lifecycle.name));
    }
    onMessage(player, message) {
        const _ = this;
        _.lifecycle.broadcast(message, [player.id]);
    }
}

Team Channel

DurableObjectCapability.Create builds a lifecycle capability from F# functions. The history capability creates the messages table before the channel handles any work, and each teammate who connects receives the last 20 messages.

open Fable.Core

module Workers = FSharp.CloudEdge.Runtime.Workers
module Runtime = FSharp.CloudEdge.Runtime.Workers.Cloudflare.Workers
module Agents = FSharp.CloudEdge.Runtime.Agents
module Lifecycle = FSharp.CloudEdge.Runtime.Agents.Lifecycle

let history (sql: Workers.SqlStorage) =
    Lifecycle.DurableObjectCapability.Create(onStart = fun _ ->
        sql.exec "CREATE TABLE IF NOT EXISTS messages (body TEXT)" |> ignore
        None)

[<AttachMembers>]
type TeamChannel(ctx: Workers.DurableObjectState<obj>, env: obj) as this =
    inherit Runtime.DurableObject<obj, obj>(ctx, env)

    let lifecycle = Lifecycle.Lifecycle.install<obj, obj>(this).``use`` (history ctx.storage.sql)

    member _.onConnect(teammate: Agents.Connection<obj>, _context: Agents.ConnectionContext) =
        let recent = ctx.storage.sql.exec<{| body: string |}>("SELECT body FROM messages ORDER BY rowid DESC LIMIT 20").toArray ()
        for message in Array.rev recent do
            teammate.send (U3.Case1 message.body)

    member _.onMessage(sender: Agents.Connection<obj>, message: Agents.WSMessage) =
        match message with
        | U3.Case1 text ->
            ctx.storage.sql.exec ("INSERT INTO messages (body) VALUES (?)", text) |> ignore
            lifecycle.broadcast (message, [| sender.id |])
        | _ -> ()

Needs a SQLite-backed Durable Object binding for TeamChannel, with channels addressed by name.

Testing and feedback

Useful test cases include agent identity, callback signatures, connection lifecycle, state recovery, and the F# lifecycle integration.

Verify bindings explains how to run checks and report an issue. Include a small reproduction and the package versions used; working examples are welcome too.

NuGet packages

Runtime.Agents 0.1.0, Runtime.Workers 0.1.0.

See installation and release availability.

Edit this page