Skip to main content

Execution budgets

Do LLM and outbound API work in tasks, not inbound methods — see Execution Model. Full platform numbers live in the StatefulActor API reference.

Task retries

A task that throws retries up to its maxRetries (default 5) with exponential backoff, then is parked — it stops retrying and no longer fires. every() tasks reset their attempt count after each successful run. The drain catches a task that throws, so the failure never leaves the alarm handler.
  • alarm(info) returns normally after a task fails. The SDK counts the attempt itself, and info.retryCount plays no part.
  • The alarm’s own redelivery applies only when the handler itself fails, as on an overrun of its budget. That retry follows the platform’s policy, not maxRetries.

Storage

Message history and state live in the actor’s persistent storage and are subject to its size caps — see the storage reference. Message history is append-only — there is no delete or trim API. last(n) reads a bounded window, but writes accumulate forever. For relational or queryable data, use the instance’s embedded SQL database instead, which the SQL guide covers. One database holds up to 1 GB, and one bound value up to 2 MiB. Since SDK 0.13.0 the message log, the state, the progress events and the scheduled tasks are kept in that same database, the one at this.ctx.storage.sql. The SDK owns every table whose name starts with _a_. Those tables count toward the database’s 1 GB. Reads stay flat as a log grows. An instance that already has data migrates itself once, on its first write after the upgrade. The SDK creates the tables through ctx.storage when an instance activates.
  • ctx.storage.sql is the engine the SDK’s tables are created in. It is the instance’s one SQL database.
  • ctx.storage.transactionSync creates every table in one transaction. Either all of them exist or none does.
Do not roll back across 0.13.0. An SDK older than the migration does not look in the SQL database and reads a migrated instance as empty. Its data is still stored, but invisible to that code. Upgrading is safe; downgrading is not.

WebSockets

The agent’s sockets are Stateful Actor WebSockets, and their limits apply unchanged.
  • A connection is capped at one hour from the handshake, even while frames flow. It ends with close code 1006, and the client reconnects to the same name, where the history, the state and the pending tasks remain.
  • Frames that arrive while a handler runs queue up to 256 events or 1 MiB. Past that, the socket closes with 1013.
  • send and broadcast go out at once, not after the write that prompted them commits. A frame that carries the message log’s seq lets a client detect a gap and read again on reconnect.

Actor id length

An agent’s name becomes an actor id, and that id is carried into names the platform builds for the instance behind it — including the file its persistent state is kept in, which is capped at 255 bytes. The budget is measured on the id once URL-escaped, so a : costs three characters rather than one, and the ceiling is 225 escaped characters: 225 ASCII characters, 54 two-byte, 36 three-byte, or 27 four-byte (emoji), plus 6 for every escaped run. encodeAgentName() and mountAgents() apply the rule. The two exports below are for a front door written by hand. Worth knowing either way: an over-long id is not rejected on first use. The instance activates and accepts writes, and only becomes unreachable once it is evicted and rehydrated, so a name that is too long looks healthy until the first eviction.

MAX_ESCAPED_ACTOR_ID_LENGTH

const MAX_ESCAPED_ACTOR_ID_LENGTH: 225 = 225
The longest an actor id may be once URL-escaped, in characters — the real limit, and the one encodeAgentName enforces. An actor id is not only an address. It is carried into names the platform builds for the instance behind it: the timer that drives its alarms, and — the tightest of them — the file its persistent state is kept in. Those names take the id URL-escaped, they add fixed decoration of their own, and they are capped at 255 characters. The largest piece of decoration leaves this many characters for the escaped id, so this is what an id is actually measured against. That the budget is spent on the ESCAPED id is the part worth internalising, because : — the one character encodeAgentName brackets an escape with — is itself escaped, to %3A, and so costs three characters of the budget rather than one. Measure an id the way the limit does with escapedActorIdLength:
Why an id over this is worth refusing up front It does not fail at the edge, and it does not fail at once. An instance with an over-long id activates, serves requests and accepts writes — the long name is only needed when the instance is loaded again after being evicted from memory, and from that point on the instance cannot be opened and what it stored is unreachable. Nothing about the first, healthy run of it says anything is wrong. So the limit is checked on the id, before an instance is ever addressed, rather than left to surface later as lost state. How many characters of NAME this allows It depends on which characters they are, because encodeAgentName escapes everything outside the addressable set and an escape is longer than what it replaces. Escaping is per RUN, and each run also spends 6 on the pair of : bracketing it: An ASCII name is written through and gets the whole 225; everything else costs more, down to 27 characters for a name of emoji. A name that mixes escaped characters with addressable ones fits less than either row suggests, since every separate run pays the 6 again. A name whose id would exceed this is refused: encodeAgentName throws, and mountAgents answers 400 malformed_name without waking an actor. Use it in a hand-written front door that addresses the same instances — or better, just call encodeAgentName, which applies this rule for you.

escapedActorIdLength()

escapedActorIdLength(id): number
How long id is under the rule MAX_ESCAPED_ACTOR_ID_LENGTH states — the length of the id once URL-escaped, which is what the platform measures. For an id from encodeAgentName this is its length plus two for every : in it, since : escapes to %3A and nothing else in an actor id is escaped at all:
Reach for it when a front door of your own has to decide whether an id fits before using it; id.length under-counts every escaped id and will accept ids that the platform later cannot name. Parameters Returns number The number of characters it occupies once escaped.

MAX_AGENT_NAME_LENGTH

const MAX_AGENT_NAME_LENGTH: 225 = MAX_ESCAPED_ACTOR_ID_LENGTH
The longest routing name the mount will address, in characters of the decoded (pre-encoding) name. This is the ceiling a name can have under MAX_ESCAPED_ACTOR_ID_LENGTH, not a second rule: every character of a name costs at least one character of the escaped actor id it encodes to, so a name longer than this cannot possibly have an id within the limit and is refused without encoding it. A name at or under it is not thereby accepted — one character of a name can cost eight or more, so MAX_ESCAPED_ACTOR_ID_LENGTH is what actually decides, and only a name spelled entirely from ASCII letters, digits, -, _, and . gets the whole length. See MAX_ESCAPED_ACTOR_ID_LENGTH for what other characters cost. A name over this length answers 400 malformed_name.

What’s shipped

  • Agent base class with message history, scheduled tasks, and merge-patch state
  • WebSocket termination via webSocket()
  • The agent socket layer (SDK ≥ 0.10.0) — AgentClient for browser/Node clients (automatic reconnect, state mirroring, typed RPC) and AgentSocketServer on the agent side, with the @rpc() opt-in
  • BlobStore for blob access within actors
  • The mount front door (SDK ≥ 0.12.0) — mountAgents() routes every agent a function serves on one address shape; see Mounting Agents
The Agent API surface is Beta and may change as pieces land.