agent boundaries
why
live interaction and durable execution fail in opposite ways: agent state dies with the connection; a workflow's whole value is surviving crashes and waits. blending them makes each fragile — durable progress lost when a socket closes, or a realtime surface blocked behind process machinery. interaction to the agent, run-to-completion to the workflow: each boundary keeps the guarantees it is good at, with typed events crossing between.
rule
an agent is the realtime identity and interaction boundary. a workflow is the durable run-to-completion boundary. use both only when the product needs both direct interaction and durable multi-step orchestration.
agent owns
- connections, streamed or websocket updates, messages, direct user input.
- starting a workflow with a typed small payload.
- sending typed approval, cancellation, or correction events to an existing workflow instance.
- rendering or broadcasting already-persisted progress.
workflow owns
- durable process state, retries, long waits, final status.
- calling a typed Agent capability only when live interaction is necessary.
- persisting the fact before announcing it to the Agent or its clients.
choose the boundary by survival requirement
| requirement | owner | reason |
|---|---|---|
| live stream, presence, ephemeral selection | agent/UI | exists only while a client interaction is active |
| user message starting/correcting durable work | agent boundary then workflow | agent validates the interaction; workflow owns the lasting process |
| retry, delay, approval wait, continuation | workflow | must survive disconnect, restart, redeploy |
| durable progress/status/history | persistent store | both agent and workflow reload the same authority |
| immediate visual freshness | agent broadcast or query invalidation | projection after the durable write, not the write itself |
decision process
| shortcut | failure | direction |
|---|---|---|
| process state only in agent memory | reconnects/restarts lose authority | persist canonical state; agent re-reads and broadcasts it |
| long operation inline in a message handler | disconnect and request lifetime become part of business success | start a typed durable workflow with a stable reference |
| workflow trusts a live agent payload as current | payload may be repeated, delayed, stale | workflow reloads the durable fact before transition |
| broadcasts as an event log | delivery is not guaranteed or exactly once | write durable record/event first; publish freshness second |
good and bad
bad
onMessage = async (message) => {this.setState({ status: "running" });await runLongProcess(message.payload); // minutes of durable work inline};
the durable process lives only in agent memory and the request lifetime: a disconnect loses the authority, a reconnect cannot recover the status. minutes of provider work hang on one socket — a flaky connection, redeploy, or client refresh kills or orphans the process with no way to see or resume progress; the user's only recourse is to start over and risk a duplicate effect.
good
onMessage = async (message) => {const instance = await this.env.workflow.start("processWorkflow",{ id: message.runId },{ runId: message.runId },);this.broadcast({ runId: message.runId, status: "started" });};
the agent validates the interaction, starts a typed durable workflow, and broadcasts a projection of persisted truth it can reload on reconnect. the interaction surface can crash, disconnect, or redeploy freely without touching process success; the workflow survives waits and retries the connection cannot. typed events keep the two synchronized without sharing fragile state.
verification
disconnect during an active process, restart the agent/runtime, reconnect: the interface must reload the same durable status and the workflow continue or stop on persisted facts alone. repeat a message or broadcast — it must not create a second semantic transition.
placement
Cloudflare Agent entrypoint in ~agents/, workflow entrypoint in
~workflows/: the prefix describes framework ownership, not business logic.
domain service, schema, mapper, and provider adapter live under their
technical-role/domain paths; the entrypoint stays a thin composition layer.
do not
- let a websocket disconnect decide whether a durable process succeeds.
- put the only workflow status in ephemeral Agent state.
- use a direct Agent RPC or broadcast as a durable handoff; those calls may be repeated when the workflow retries.
- use an agent loop as a substitute for a durable workflow when the process has distinct retry, approval, or continuation boundaries.