Skip to content

Commit f1fe57d

Browse files
blog: Harness post + Shiki typography (#1609)
Co-authored-by: anthony <142696453+anthonyiscoding@users.noreply.github.com>
1 parent 1cdc9d4 commit f1fe57d

14 files changed

Lines changed: 664 additions & 28 deletions

blog/.nvmrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
22

blog/astro.config.mjs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,12 @@ export default defineConfig({
1010
build: {
1111
format: 'directory',
1212
},
13+
markdown: {
14+
syntaxHighlight: 'shiki',
15+
shikiConfig: {
16+
theme: 'github-dark-dimmed',
17+
wrap: false,
18+
},
19+
},
1320
integrations: [mdx()],
1421
})

blog/package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
"private": true,
44
"license": "Apache-2.0",
55
"version": "0.1.0",
6+
"engines": {
7+
"node": ">=22.12.0"
8+
},
69
"type": "module",
710
"description": "iii.dev/blog — static blog built with Astro",
811
"scripts": {
@@ -15,7 +18,8 @@
1518
"dependencies": {
1619
"@astrojs/mdx": "^5.0.4",
1720
"@astrojs/rss": "^4.0.18",
18-
"astro": "^6.2.2"
21+
"astro": "^6.2.2",
22+
"sharp": "^0.34.5"
1923
},
2024
"devDependencies": {
2125
"@astrojs/check": "^0.9.4",
31.9 KB
Loading
38.6 KB
Loading
57.2 KB
Loading

blog/src/content.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ const blog = defineCollection({
1010
updatedDate: z.coerce.date().optional(),
1111
draft: z.boolean().optional().default(false),
1212
tags: z.array(z.string()).optional().default([]),
13+
author: z.string().optional(),
1314
}),
1415
})
1516

Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
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+
![Add a worker — every category collapses into the same primitive](../../assets/blog/add-a-worker/banner.png)
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+
![One worker exposes functions; triggers chain those functions across other workers](../../assets/blog/add-a-worker/orders-worker-chain.png)
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+
![An agent is just another worker, composing with every other primitive in the system](../../assets/blog/add-a-worker/orchestrator-network.png)
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).

blog/src/content/blog/hello-world.md

Lines changed: 0 additions & 14 deletions
This file was deleted.

0 commit comments

Comments
 (0)