|
| 1 | +--- |
| 2 | +title: 'Add a worker' |
| 3 | +description: 'The cloud is a bazaar, and that was okay until agents arrived and had to contend with 10,000 shops. Three primitives, one engine, and one answer to every question.' |
| 4 | +pubDate: 2026-05-05 |
| 5 | +author: 'Mike Piccolo, Founder & CEO of iii' |
| 6 | +tags: ['agents', 'architecture', 'workers', 'cloudflare'] |
| 7 | +--- |
| 8 | + |
| 9 | + |
| 10 | + |
| 11 | +The cloud is a bazaar, and that was okay until agents arrived and had to |
| 12 | +contend with 10,000 shops. |
| 13 | + |
| 14 | +Cloudflare opened their Agents Week with a line that names this directly: |
| 15 | +*"The Internet wasn't built for the age of AI. Neither was the cloud."* When |
| 16 | +I read it, I agreed. But I think the reason the cloud wasn't built for agents |
| 17 | +is older than agents. The cloud feels barely even built for humans. |
| 18 | + |
| 19 | +The cloud was built as a cloud. Compute is a product. State is a product. |
| 20 | +Sandbox is a product. Egress is a product. Tool discovery is a product. |
| 21 | +Payment is a product. Queues are a product. Each product has its own API, its |
| 22 | +own lifecycle, its own failure mode, its own billing, its own on-call |
| 23 | +rotation. You choose which to adopt and integrate them in application code. |
| 24 | + |
| 25 | +This organization is so naturalized that it's hard to see it as a choice. |
| 26 | +But it is a choice, and it carries a tax. Every boundary between products is |
| 27 | +a place to write integration code, correlate logs, debug, and get paged on a |
| 28 | +Saturday. Developers have quietly paid this invisible tax for decades. |
| 29 | +Agents are making this tax visible. |
| 30 | + |
| 31 | +## The cloud tax, compounding |
| 32 | + |
| 33 | +A typical web request touches three or four services and completes in 300 |
| 34 | +milliseconds. You notice the boundaries on a bad day. An agent reasoning loop |
| 35 | +touches more. The agent needs a compute environment, a sandbox, a state |
| 36 | +store, a credential broker, a tool registry, an identity service, a queue, |
| 37 | +an observability pipeline, sometimes a payment service. It crosses those |
| 38 | +boundaries not once but repeatedly, over minutes or hours, in a single task. |
| 39 | + |
| 40 | +The cost of each crossing is specific and cumulative. A single user-visible |
| 41 | +failure can be three retries deep and impact three different teams with |
| 42 | +three independent budgets before anyone notices. |
| 43 | + |
| 44 | +This is the cloud tax, and agents pay it on every loop iteration. |
| 45 | + |
| 46 | +Cloudflare's Agents Week is a clear-eyed response to this problem. Their |
| 47 | +answer: build isolate-level primitives inside their cloud at the category |
| 48 | +level. A better compute primitive (V8 isolates, 100x faster and more |
| 49 | +memory-efficient than containers). A better state primitive (Durable Objects |
| 50 | +with per-instance SQLite). A new sandbox primitive (Sandboxes GA). A new |
| 51 | +egress primitive (Outbound Workers for zero-trust credential policy). A new |
| 52 | +tool-discovery primitive (MCP). A new payment primitive (x402). A new CLI |
| 53 | +that agents themselves can drive. |
| 54 | + |
| 55 | +Each of these is a serious piece of engineering. The isolate work in |
| 56 | +particular solves a hard problem: per-agent economics at the scale agents |
| 57 | +actually require. Cloudflare's math on this is correct and worth |
| 58 | +internalizing. One hundred million US knowledge workers at 15% concurrency |
| 59 | +is 24 million simultaneous agent sessions. At current compute density, we |
| 60 | +are not a little short. We are orders of magnitude short. Isolates close |
| 61 | +that gap in a way containers cannot. That is real. |
| 62 | + |
| 63 | +But I think the move accepts a premise it doesn't examine: that the right |
| 64 | +shape for all infrastructure can be defined above the isolate level. The |
| 65 | +reason Cloudflare has missed this is simple — their business is selling |
| 66 | +multiple categories of infrastructure. A primitive that collapses all |
| 67 | +infrastructure categories risks significantly lowering switching costs |
| 68 | +between them and their competitors. |
| 69 | + |
| 70 | +## The shape of the question |
| 71 | + |
| 72 | +Every time an agent needs a new capability, the cloud model asks the same |
| 73 | +sequence of questions. Which product? Which API? Which config? Which |
| 74 | +lifecycle? Which billing? Which on-call? The questions multiply with the |
| 75 | +capabilities. An agent that needs eight things has adopted eight products. |
| 76 | + |
| 77 | +The cloud teaches you to think in categories. Each category is its own |
| 78 | +world, with its own ontology and its own integration story. You learn the |
| 79 | +queue world, then the identity world, then the observability world. The |
| 80 | +expertise requirements compound, quadratically with the surface area. Every |
| 81 | +new category you bring in for your agent is a new boundary the agent has to |
| 82 | +cross at runtime, with no guarantee the behavior or the format or the |
| 83 | +vocabulary will match what's on the other side. |
| 84 | + |
| 85 | +I think the answer can be the same every time. |
| 86 | + |
| 87 | +## Three primitives |
| 88 | + |
| 89 | +When I first started building backends, I took the cloud and its hidden |
| 90 | +costs for granted. Agents made that cost visible. Every loop iteration |
| 91 | +crossed boundaries that weren't designed to compose, and the cost compounded |
| 92 | +faster and faster. For over a decade I thought the answer was a better |
| 93 | +cloud: better products, better APIs, better integrations. Eventually I |
| 94 | +realized the answer was a smaller surface, not a bigger one. The right |
| 95 | +primitive is small enough to absorb every category. |
| 96 | + |
| 97 | +This is what iii does by adhering to 3 simple primitives, or shapes that can |
| 98 | +encapsulate everything else: |
| 99 | + |
| 100 | +- A **Function** is a unit of work with a stable identifier |
| 101 | + (`cloudflare::workers::r2::get`, `azure::blob::get`, `agents::researcher`) |
| 102 | + that receives input, optionally returns output, and can live in any |
| 103 | + process in any language. Functions invoke other functions through |
| 104 | + `trigger()`. The engine handles routing, serialization, and delivery. |
| 105 | +- A **Trigger** is what causes a function to run. You register a trigger to |
| 106 | + bind any event source to any function: an HTTP endpoint, a cron schedule, |
| 107 | + a queue subscription, a state change, a stream event. Triggers are |
| 108 | + declarative. The function doesn't change when you add a new way to invoke |
| 109 | + it. |
| 110 | +- A **Worker** is any process that connects to the engine and registers |
| 111 | + functions and triggers. |
| 112 | + |
| 113 | + |
| 114 | + |
| 115 | +The queue is a worker. Cron is a worker. The state store is a worker. The |
| 116 | +HTTP front door is a worker. The egress proxy is a worker. The tool |
| 117 | +registry is a worker. The payment gateway is a worker. The observability |
| 118 | +pipeline is a worker. A TypeScript API service is a worker. A Python ML |
| 119 | +pipeline is a worker. A Rust microservice is a worker. A hardware-isolated |
| 120 | +microVM sandbox is a worker. A browser tab is a worker. |
| 121 | + |
| 122 | +And an agent is a worker. |
| 123 | + |
| 124 | +Your application code connects as workers too. There's no separate ontology |
| 125 | +for "infrastructure" and "application" and "agent." There's one model, and |
| 126 | +the differences live in what each worker does, not in what kind of thing it |
| 127 | +is. |
| 128 | + |
| 129 | +That fact is what makes the following section possible. |
| 130 | + |
| 131 | +## Consider the agent |
| 132 | + |
| 133 | +Consider the list of things agents need. The same list Cloudflare organized |
| 134 | +an entire product week around. |
| 135 | + |
| 136 | + |
| 137 | + |
| 138 | +An agent needs persistent state between reasoning steps. **Add a worker.** |
| 139 | +The state worker exposes scoped key-value operations. The agent writes |
| 140 | +progress through `trigger()`, reads it on re-entry, and continues from where |
| 141 | +it left off. |
| 142 | + |
| 143 | +An agent needs to run untrusted code in an isolated sandbox with its own |
| 144 | +filesystem, shell, and network. **Add a worker.** Sandbox workers in iii are |
| 145 | +hardware-isolated microVMs, each with its own root filesystem, network |
| 146 | +stack, and process tree. `iii worker add ./my-project`. The engine manages |
| 147 | +the VM lifecycle and auto-detects Node, Python, or Rust. |
| 148 | + |
| 149 | +An agent needs to discover what tools and capabilities exist in the system |
| 150 | +right now. iii keeps a live catalog. That catalog is the same and always up |
| 151 | +to date whether it's being served over MCP, A2A, or some other protocol not |
| 152 | +yet invented. These protocols all become workers and can all share the same |
| 153 | +single source of truth: iii. **Add a worker.** |
| 154 | + |
| 155 | +An agent needs to speak MCP to an external tool server. **Add a worker** that |
| 156 | +bridges MCP into `trigger()` calls. The bridge is a worker like any other. |
| 157 | + |
| 158 | +An agent needs identity and authorization boundaries for sensitive actions. |
| 159 | +**Add a worker** that enforces the boundary, and scope access through the |
| 160 | +engine's worker RBAC. |
| 161 | + |
| 162 | +An agent needs to call a payment service. **Add a worker.** |
| 163 | + |
| 164 | +An agent needs durable multi-step execution with retries, backoff, and |
| 165 | +dead-letter queues. **Add a worker.** We already ship a worker that does |
| 166 | +this. Configure retry policy, backoff, FIFO ordering, and DLQ per queue. |
| 167 | +Each step is a function. Chaining them is a `trigger()` call with an |
| 168 | +enqueue action. |
| 169 | + |
| 170 | +An agent needs to run on a different continent. **Add a worker** there. The |
| 171 | +code, the primitives, and the composition model don't change. |
| 172 | + |
| 173 | +The title of this post isn't a slogan. It's the answer to every |
| 174 | +infrastructure integration question. |
| 175 | + |
| 176 | +## One trace, not eight |
| 177 | + |
| 178 | +The research-steps queue's retry policy, backoff, FIFO ordering, and |
| 179 | +dead-letter behavior live in `iii-config.yaml`. If the worker crashes |
| 180 | +mid-step, the queue redelivers. The function reads state on entry, sees |
| 181 | +what already completed, and continues from there. |
| 182 | + |
| 183 | +The observability story is the part that genuinely changes compared to the |
| 184 | +cloud model. Every function invocation carries a trace ID. Every |
| 185 | +`trigger()` call propagates OpenTelemetry context across workers, across |
| 186 | +queues, across language boundaries. Logs emitted through the iii Logger |
| 187 | +attach automatically to the active span. When an agent step enqueues the |
| 188 | +next step, which writes state, which fires a downstream worker in a |
| 189 | +different language, the entire chain is one trace, in one tool, with spans |
| 190 | +and logs already correlated. |
| 191 | + |
| 192 | +One trace, instead of eight products' worth of timestamp archaeology. |
| 193 | + |
| 194 | +## Composition, not runtime |
| 195 | + |
| 196 | +There are workloads for which Cloudflare's shape is exactly right. If your |
| 197 | +agent needs a V8 isolate in 50 milliseconds, on an edge node 30 milliseconds |
| 198 | +from your user, with a per-instance SQLite database you can query without a |
| 199 | +network hop, Cloudflare built that for you and you should use it. |
| 200 | + |
| 201 | +iii doesn't ship managed isolates, Durable Objects, a native MCP |
| 202 | +implementation today, an x402 payment primitive, or a zero-trust egress |
| 203 | +platform. iii ships the runtime that makes them all behave like one |
| 204 | +cohesive end-to-end application — and a worker registry that makes adding |
| 205 | +functionality as easy as `iii worker add iii-http`. |
| 206 | + |
| 207 | +iii doesn't try to build the best version of every category. iii lets you |
| 208 | +seamlessly pick and choose the best version of every category. iii asks |
| 209 | +whether the categories need to exist at all, or whether they're patterns |
| 210 | +that emerge from composing a small number of primitives. A payment |
| 211 | +integration is a worker. A zero-trust egress policy is a worker. An MCP |
| 212 | +bridge is a worker. Each is a pattern on top of the same workers, triggers, |
| 213 | +and functions. |
| 214 | + |
| 215 | +## The bet |
| 216 | + |
| 217 | +The cloud wasn't built for agents. Making the cloud better is one answer. |
| 218 | +Collapsing the cloud into a primitive is another. |
| 219 | + |
| 220 | +Three primitives, one engine, and one answer to every question: **add a |
| 221 | +worker.** |
| 222 | + |
| 223 | +iii is open source. Get started with our [quickstart](https://docs.iii.dev). |
0 commit comments