# The Harness Is the Backend
**作者**: Mike Piccolo
**日期**: 2026-04-28T14:49:59.000Z
**来源**: [https://x.com/mfpiccolo/status/2049139067359568032](https://x.com/mfpiccolo/status/2049139067359568032)
---

The most important architectural question in AI infrastructure right now isn’t which model to use. It’s how much infrastructure is required to build something useful with it.
Anthropic, OpenAI, CrewAI, LangChain all call that wrapping the agent harness. The harness includes the orchestration loop, tools (MCP, A2A), memory, context management, and error handling that make a model useful. They all agree the model isn’t the product. The infrastructure is. They disagree deeply on how much of it should exist.
Anthropic keeps their harness thin. It’s an elegant loop: Assemble the prompt, call the model, execute tool calls, and repeat. The model decides everything. OpenAI adds more structure: instruction stacks, orchestration modes, and explicit handoff patterns. CrewAI takes a multi-pronged approach: deterministic Flows for routing and validation, autonomous agents for the rest. LangGraph has the biggest harness Every decision is a node, every transition a defined edge, the entire workflow encoded in the harness.
The spectrum runs from strongly trusting the model and weakly encoding the logic to weakly trusting the model, and strongly encoding the logic. And every team building with agents has to choose what size of harness they need.
But there’s an assumption buried in the debate that nobody is questioning: that the harness is extrinsic to the traditional backend.
The agent’s loop, its tools, and its memory live in one layer: the harness. While execution infrastructure such as queues, state, HTTP routing, server side rendering, observability, and all other backend components live in another: “the backend”.
I believe that this is temporary and it’s just a small step along the way to true adoption and acceptance of agentic infrastructure into “the backend”.
## How Agents Work Today
Here’s how most agentic architectures work. The harness is a Python process (or TypeScript, or a managed framework) that wraps the model. When the agent decides to act, the harness translates a tool call into an HTTP request which in turn triggers something to happen on the backend like a queue publish or a database write. The backend is its own world that is kept separate from the agents.
The harness retries on its own schedule, the queue retries on its own conditions, and the HTTP layer manages its own timeouts. There is no trace directly connecting these disparate systems. When something breaks debugging means correlating logs across systems and reconstructing observed behavior. This is a common process in backend engineering but whereas prior systems were largely deterministic, agents are stochastic at best. .
With every additional agent the probabilities widen and at the most basic level are agents^2 * services. Put another way, 1 agent and 5 backend systems is 5 stochastic paths to debug. 4 agents and 5 backend systems are 80 stochastic paths to debug.
There is no good way to make agents more deterministic, much of their basic functionality is intended to give varied answers for similar and even identical inputs. They’re not stochastic by chance, they’re stochastic by intention because they make computers useful in a brand new way. The billion dollar question is how to handle agents properly by creating the correct harnesses in the correct contexts.
## Taking a Step Back
The fundamental promise of harnesses today is that they are trying to operate a new paradigm (stochastic LLMs) within an old one (deterministic backends). It’s not that construction of agent harnesses is inherently wrong, it’s that effective solutions must begin with the deconstruction of what a backend is.
Most of us have up until very recently taken the backend and how it works for granted; including myself. Without agents and the LLMs that power them I probably would have never thought about this problem before. So I embarked on a journey to figure out the fundamental building blocks of a backend.
At first I thought that backends are collections of services that exist in categories of products and are assembled with libraries, integrations, architecture diagrams, orchestration code, and the list kept growing. Eventually I realized that I was approaching this solution from the top down instead of bottom up. Once I realized that the backend became very simple:
A backend is composed of three essential elements: workers that orchestrate work, triggers that invoke these services, and functions within the services that do the actual work.
## Abstracting the Backend
Once I realized this it became clear that I, and my very talented team, could build a backend using this abstraction. Far from an academic exercise we’ve found this abstraction has very real utility both in the agentic world and more broadly as our abstraction completely encapsulates the execution context of “the backend”. So we built iii to make that abstraction available to everyone.
iii works just like my description above:
It defines Function as a unit of work with a stable identifier (ex. orders::validate) that receives input, and optionally returns output it can live in any process, and in any language.
A Trigger is what causes a function to run; it can be a direct call to a function, an HTTP endpoint, a cron schedule, a queue subscription, a state change, a stream event, or anything else. Triggers are declarative: the worker says “this function runs when this thing happens,” and iii handles routing, serialization, and delivery.
A Worker is any process that connects to the engine and registers functions and triggers.
A TypeScript API service is a worker. A Python ML pipeline is a worker. A Rust microservice is a worker. And an agent is a worker.
This is the idea that changes everything. An agent connects to the engine, registers functions and triggers, persists context through state::set, hands off work through queue-backed triggers, and broadcasts results via pub/sub. It doesn’t call “the backend” through a separate integration layer. It participates in the same system, with the same primitives, as everything else.
```
const iii = registerWorker('ws://localhost:49134', { workerName: 'agentic-backend' })
iii.registerFunction('agents::researcher', async (data) => { // the unit of work
// Python Worker: requests + duckduckgo-search
const sources = await iii.trigger({
function_id: 'web::search',
payload: { query: data.topic, limit: 10 }
})
// Rust Worker: scraper + tokio, fetched in parallel
const pages = await iii.trigger({
function_id: 'web::scrape',
payload: { urls: sources.map(s => s.url) }
})
// TypeScript Worker: wraps the OpenAI SDK
const findings = await iii.trigger({
function_id: 'llm::summarize',
payload: { topic: data.topic, documents: pages }
})
await iii.trigger({ // Rust Worker: persist to shared state
function_id: 'state::set',
payload: { scope: 'research-tasks', key: data.task_id, value: findings }
})
iii.trigger({ // TypeScript Worker: hand off to the critic
function_id: 'agents::critic',
payload: { task_id: data.task_id },
action: TriggerAction.Enqueue({ queue: 'agent-tasks' }) // run in the queue
})
return findings
})
iii.registerTrigger({ // HTTP entrypoint
type: 'http',
function_id: 'agents::researcher',
config: { api_path: '/agents/research', http_method: 'POST' }
})
iii.registerTrigger({ // also runs on a pending state row
type: 'state',
function_id: 'agents::researcher',
config: { scope: 'research-tasks', condition: 'status == "pending"' }
})
```
Three calls. registerFunction defines the work. registerTrigger binds it to the world — in this case an HTTP endpoint and a state change reaction, for the same function. The researcher is now callable via a POST request and automatically fires whenever a research task enters a pending state. Add another trigger and it also runs on a cron schedule. The function doesn’t change. The triggers compose.
The agent stores state with the same trigger() call a payment service would use. It hands off to the critic through the same queue mechanism an order pipeline would use. The agent’s “tools” are functions. Its “memory” is state. Its “orchestration” is triggers and composition. There is no special agent infrastructure because there doesn’t need to be.
The harness is the backend.
## Workers all the way down
This goes deeper than agents fitting into a backend. It’s about what iii considers a primitive and what happens when one primitive, in just a few lines of code, is the answer to every question.
In most platforms, every new capability is a new category. Need queues? Evaluate queue products. Need streaming? Different product. Sandboxing? Another. Each has its own internals, its own lifecycle, its own integration story. The platform is a catalog. Your job is to shop it and assemble it.
In iii, the answer to almost any question is the same: add a worker, which in turn registers triggers and functions.
I want sandboxing. Add a worker. I want an agent that researches topics. Add a worker. I want real-time streaming. Add a worker. I want go-to-market capabilities like lead scoring, email sequences, CRM sync. Add a worker. I want cron scheduling. It’s already a worker. I want observability. Already a worker.
The worker connects, registers what it can do, and the system absorbs it: live, discoverable, observable. The answer doesn’t change based on what kind of capability you’re adding. It doesn’t change based on language, or whether it’s infrastructure or business logic, or whether a human or an agent is creating it. Add a worker.
This is not just architectural uniformity. It’s a collapse of categories. In traditional systems, every capability lives in its own ontology. Queues have broker semantics, HTTP has routing semantics, cron has scheduling semantics, agents have orchestration semantics. In iii, they are all the same thing: a process that registers functions and triggers. The semantics live in the functions, not in the infrastructure.
Paradigm shifts in software don’t add features. They collapse categories. “Everything is a file” made Unix composable. Components as functions made React’s mental model stick. In iii, the answer is always “add a worker.” That’s the primitives. That’s the whole model.
## A live system
Because everything is a worker, three properties emerge that traditional architectures cannot produce:
Live discovery. When a worker connects, it receives the full catalog of every function registered across every other worker. When new functions appear, every worker gets notified. When a worker disconnects, every worker is notified. The engine is the single source of truth.
For agents, this is also cognitive infrastructure. An agent can see exactly what the entire system can do right now. There is no risk of an agent receiving outdated context.
Live extensibility. Add new workers and capabilities to a running iii system without redeploying or redesigning the architecture. There are no config changes and no restarts, because the system extends at runtime
This is how agentic systems actually want to operate. You don’t ever need to interrupt production to add a new capability. You connect a new worker, its functions distribute across the system, and any agent that can use them at will; or even extend the system with their own workers.
Live observability. iii’s observability is built on OpenTelemetry. Every function invocation carries a trace ID. Every trigger() call propagates it across workers, across languages, across queue handoffs. Every log emitted through the iii Logger is automatically correlated to the active trace and span, emitted as structured OpenTelemetry LogRecords, and routed to whichever backend you use: the iii Console, Grafana, Jaeger, Datadog. This isn’t a separate component to install and integrate, it’s just another worker Traces, metrics, and structured logs are produced by the engine itself, not by application-level middleware.
When an agent calls a tool that enqueues a message that triggers a downstream function that writes to state, the entire chain is one trace. Not three separate systems connected with timestamp correlation or manually tracked trace ids. One trace, across languages, across workers, across the agent-backend boundary. You go from a slow waterfall span directly to the correlated logs that explain what happened.
## Agents that create workers
Here is where the model gets truly recursive.
iii supports hardware-isolated microVM workers with hardware isolation. The sandbox functionality itself is a worker with its own filesystem, network stack, and process tree. You create a worker with a single command: iii worker add ./my-worker. The sandbox worker connects to the engine, registers functions and triggers, and participates in the system exactly like every other worker.
Now consider what happens when an agent can do this.
An agent worker can also spin up a new sandbox worker at runtime. That sandbox gets its own isolated environment. It registers its own functions and triggers. Those functions immediately appear in the live catalog. Other agents and services can invoke them. When the sandbox is no longer needed, it disconnects and its functions unregister.
The sandbox is not a separate “sandbox product.” It is a worker, using the same primitives as everything else it just happens to provide hardware isolation. An agent creating a sandbox worker is just one worker creating another.
This is what it looks like when infrastructure becomes a design pattern instead of a product category. Need isolated execution for untrusted code? That’s a sandbox worker. Need a temporary specialist agent? Spin up a worker, register functions, shut it down when finished. Need a fleet of parallel task executors? Have a worker spin up other workers. The primitive is the same. The pattern varies.
## The distinction disappears
Go back to the harness debate. Anthropic says thin. LangGraph says thick. They’re arguing about how much cognitive structure to encode around the model. The thin-vs-thick debate matters, but it’s a question within a design space, not about the design space itself.
When agents are workers, thin versus thick is just a question of how many functions you register and how you compose them. A thin harness is an agent worker with a few functions that lets the model decide what to trigger() next. A thick harness is an agent worker with more functions, explicit approval gates, and conditional logic before enqueuing the next step. It’s the same primitives and system, but a different pattern.
The scaffolding metaphor shifts too. The industry talks about harness scaffolding as temporary. As models improve, you remove it. Manus has described rebuilding Claude’s agent framework four times, with each rewrite the result of discovering a better way to shape context. Claude Code strips planning steps as new models absorb the capability.
If the harness is built from the same primitives as the rest of the backend, then removing scaffolding just means simplifying a function. You don’t rearchitect an integration layer. You don’t rebuild the interface between two systems. You just register fewer functions, or compose them differently.
## Anything is a worker
A worker is anything that can open a WebSocket, register a function, and speak the primitives interface. There is no constraint on what that thing is or what language it’s written in.
iii ships SDKs for TypeScript, Python, and Rust. But those aren’t the boundaries of the system. They’re three implementations of an open wire protocol: JSON over WebSocket. The engine doesn’t know what language is on the other end of the connection. It sees functions, triggers, and a connection. If your team writes Go, or Java, or Swift, or Zig, you write a small SDK that speaks the protocol and you’re a first-class participant. The primitives interface is the contract. Everything else is a design pattern.
This means the set of what can be a worker is genuinely unbounded. A Node.js service. A Python ML pipeline. An agent. A queue. A sandbox running inside a microVM. A browser. iii ships a browser SDK, so a tab on someone’s laptop can register functions, participate in live discovery, invoke backend functions, and be invoked by backend functions. The browser is in the system the same way a Kubernetes pod is.
A Raspberry Pi is a worker. An IoT sensor at the edge is a worker. A phone running a thin client is a worker. A CI runner that spins up, registers a function, does work, and disconnects is a worker. The engine doesn’t distinguish between these. Every new language, every new device, every new runtime that implements the primitives interface gets the full system for free: live discovery, live extensibility, live observability, durable triggers, cross-everything invocation. Not because we built a special integration for each one, but because the primitive allows this composition.
## The bet
The industry is debating how much scaffolding to wrap around the model. That debate matters, but it takes for granted that the harness is its own world, separate from the backend and separate from the infrastructure that actually runs when a tool fires.
iii makes a different bet: that the right primitives (worker, trigger, function) are small enough and universal enough that the question “what can participate in this system?” has the answer: anything. A cloud service. An agent. A browser. A microcontroller. A sandbox an agent just spun up. They all compose the same way. They all discover each other. They all trace the same.
When you stop treating “agent infrastructure” as separate from “backend infrastructure,” and when you stop treating any category of participant as architecturally different from any other, the system simplifies in a way that adding features never achieves. The boundaries between harness and backend, between cloud and edge, between infrastructure and application, and between human-written services and agent-created workers all dissolve into the same three primitives.
The harness isn’t on top of the backend. The harness is a part of the backend. And the backend is whatever connects to iii.
When you get the primitives right, the categories collapse and complexity is radically simplified.
iii is open source. Get started with our quickstart.
## 相关链接
- [Mike Piccolo](https://x.com/mfpiccolo)
- [@mfpiccolo](https://x.com/mfpiccolo)
- [113K](https://x.com/mfpiccolo/status/2049139067359568032/analytics)
- [has described](https://vrungta.substack.com/p/claude-code-architecture-reverse)
- [iii](https://iii.dev/)
- [Get started](https://iii.dev/docs/quickstart)
- [Upgrade to Premium](https://x.com/i/premium_sign_up)
- [10:49 PM · Apr 28, 2026](https://x.com/mfpiccolo/status/2049139067359568032)
- [113.9K Views](https://x.com/mfpiccolo/status/2049139067359568032/analytics)
- [View quotes](https://x.com/mfpiccolo/status/2049139067359568032/quotes)
---
*导出时间: 2026/4/29 17:23:31*
---
## 中文翻译
# 马具即后端
**作者**: Mike Piccolo
**日期**: 2026-04-28T14:49:59.000Z
**来源**: [https://x.com/mfpiccolo/status/2049139067359568032](https://x.com/mfpiccolo/status/2049139067359568032)
---

目前,AI 基础设施中最重要的架构问题不是使用哪个模型,而是用它构建有用之物需要多少基础设施。
Anthropic、OpenAI、CrewAI 和 LangChain 都将这种封装称为智能体“马具”。马具包括编排循环、工具(MCP、A2A)、记忆、上下文管理和错误处理,正是这些让模型变得有用。他们都一致认为模型不是产品,基础设施才是。但对于应该存在多少基础设施,他们分歧巨大。
Anthropic 保持他们的马具极简。这是一个优雅的循环:组装提示词,调用模型,执行工具调用,然后重复。模型决定一切。OpenAI 增加了更多结构:指令栈、编排模式和显式的交接模式。CrewAI 采取多管齐下的方法:用于路由和验证的确定性流,其余部分则由自主智能体处理。LangGraph 拥有最厚重的马具:每一个决策都是一个节点,每一次转换都是一条定义好的边,整个工作流被编码在马具之中。
这个光谱从高度信任模型且弱编码逻辑,延伸到低度信任模型且强编码逻辑。每一个使用智能体构建的团队都必须选择他们需要多大尺寸的马具。
但在争论中隐藏着一个没人质疑的假设:即马具独立于传统后端之外。
智能体的循环、工具和记忆存在于一个层级:马具。而执行基础设施(如队列、状态、HTTP 路由、服务端渲染、可观测性以及所有其他后端组件)则存在于另一个层级:“后端”。
我相信这种状况是暂时的,这只是将智能体基础设施真正接纳并入“后端”的漫长道路上的一小步。
## 当今智能体如何工作
大多数智能体架构的工作方式如下。马具是一个封装模型的 Python 进程(或 TypeScript 进程,或托管框架)。当智能体决定行动时,马具将工具调用转化为 HTTP 请求,进而触发后端发生某些操作,例如发布消息到队列或写入数据库。后端是一个独立的世界,与智能体保持分离。
马具按自己的计划重试,队列按自己的条件重试,HTTP 层管理自己的超时。这些异构系统之间没有直接的追踪链路。当出现问题时,调试意味着跨系统关联日志并重建观察到的行为。这是后端工程中的常见流程,但与以前的系统大部分是确定性的不同,智能体充其量只是随机性的。
每增加一个智能体,概率范围就会扩大,在最基本的层面上,其数量等于 智能体² × 服务。换句话说,1 个智能体和 5 个后端系统意味着 5 条随机调试路径。4 个智能体和 5 个后端系统意味着 80 条随机调试路径。
没有好的方法能让智能体变得更确定,因为它们的大部分基本功能就是为了给相似甚至相同的输入提供不同的答案。它们并非偶然具有随机性,而是有意为之,因为这以一种全新的方式让计算机变得有用。价值连城的问题在于,如何在正确的上下文中创建正确的马具,从而正确地处理智能体。
## 退后一步
当今马具的基本承诺是,它们试图在一个旧范式(确定性后端)中运行一个新范式(随机性 LLM)。这并不是说构建智能体马具本身是错误的,而是说有效的解决方案必须始于解构“后端”到底是什么。
直到最近,我们大多数人(包括我自己)都想当然地接受了后端及其工作方式。如果没有智能体和驱动它们的 LLM,我可能永远不会思考这个问题。于是,我踏上了一段旅程,去弄清楚后端的基本构建块。
起初,我认为后端是存在于产品类别中的服务集合,通过库、集成、架构图、编排代码组装而成,这个清单还在不断增长。最终,我意识到我是自上而下而不是自下而上地接近这个解决方案的。一旦意识到这一点,后端变得非常简单:
一个后端由三个基本要素组成:编排工作的 Workers、调用这些服务的 Triggers,以及服务中执行实际工作的 Functions。
## 抽象后端
一旦意识到了这一点,我和我那才华横溢的团队就很清楚,我们可以使用这种抽象来构建一个后端。这绝非学术练习,我们发现这种抽象在智能体世界以及更广泛的领域中都具有非常真实的效用,因为我们的抽象完全封装了“后端”的执行上下文。因此,我们构建了 iii 以使这种抽象可供每个人使用。
iii 的工作方式正如我上面的描述:
它定义 Function 为一个具有稳定标识符(例如 orders::validate)的工作单元,它接收输入,并可选地返回输出,它可以存在于任何进程、任何语言中。
Trigger 是导致函数运行的原因;它可以是直接调用函数、HTTP 端点、cron 计划、队列订阅、状态变更、流事件或任何其他东西。Triggers 是声明式的:Worker 说“当这个事情发生时,这个函数运行”,然后 iii 处理路由、序列化和传递。
Worker 是连接到引擎并注册 Functions 和 Triggers 的任何进程。
TypeScript API 服务是 Worker。Python ML 流水线是 Worker。Rust 微服务是 Worker。而智能体也是 Worker。
这就是改变一切的想法。智能体连接到引擎,注册 Functions 和 Triggers,通过 state::set 持久化上下文,通过队列支持的 Triggers 交接工作,并通过 pub/sub 广播结果。它不通过单独的集成层调用“后端”。它与所有其他事物使用相同的系统,相同的原语。
```
const iii = registerWorker('ws://localhost:49134', { workerName: 'agentic-backend' })
iii.registerFunction('agents::researcher', async (data) => { // 工作单元
// Python Worker: requests + duckduckgo-search
const sources = await iii.trigger({
function_id: 'web::search',
payload: { query: data.topic, limit: 10 }
})
// Rust Worker: scraper + tokio, 并行获取
const pages = await iii.trigger({
function_id: 'web::scrape',
payload: { urls: sources.map(s => s.url) }
})
// TypeScript Worker: 封装 OpenAI SDK
const findings = await iii.trigger({
function_id: 'llm::summarize',
payload: { topic: data.topic, documents: pages }
})
await iii.trigger({ // Rust Worker: 持久化到共享状态
function_id: 'state::set',
payload: { scope: 'research-tasks', key: data.task_id, value: findings }
})
iii.trigger({ // TypeScript Worker: 移交给批评者
function_id: 'agents::critic',
payload: { task_id: data.task_id },
action: TriggerAction.Enqueue({ queue: 'agent-tasks' }) // 在队列中运行
})
return findings
})
iii.registerTrigger({ // HTTP 入口
type: 'http',
function_id: 'agents::researcher',
config: { api_path: '/agents/research', http_method: 'POST' }
})
iii.registerTrigger({ // 也在待处理状态行上运行
type: 'state',
function_id: 'agents::researcher',
config: { scope: 'research-tasks', condition: 'status == "pending"' }
})
```
三次调用。registerFunction 定义工作。registerTrigger 将其绑定到世界 —— 在这种情况下,是同一个函数的 HTTP 端点和状态变更反应。现在研究人员可以通过 POST 请求被调用,并且每当研究任务进入待处理状态时都会自动触发。添加另一个 Trigger,它也可以按 cron 计划运行。函数不改变。Triggers 进行组合。
智能体使用与支付服务相同的 trigger() 调用来存储状态。它使用与订单流水线相同的队列机制移交给批评者。智能体的“工具”是函数。它的“记忆”是状态。它的“编排”是 Triggers 和组合。不存在特殊的智能体基础设施,因为根本不需要。
马具即后端。
## Worker 到底
这比智能体融入后端更深层。这关乎 iii 认为的原语是什么,以及当一个原语——仅需几行代码——成为所有问题的答案时会发生什么。
在大多数平台中,每一种新能力都是一个新类别。需要队列?评估队列产品。需要流处理?不同的产品。沙箱隔离?又一个。每个都有自己的内部逻辑、自己的生命周期、自己的集成故事。平台是一个目录。你的工作是采购并组装它们。
在 iii 中,几乎任何问题的答案都是一样的:添加一个 Worker,它进而注册 Triggers 和 Functions。
我想要沙箱隔离。添加一个 Worker。我想要一个研究主题的智能体。添加一个 Worker。我想要实时流处理。添加一个 Worker。我想要进入市场的能力,如线索评分、邮件序列、CRM 同步。添加一个 Worker。我想要 cron 调度。它已经是一个 Worker 了。我想要可观测性。已经是 Worker 了。
Worker 连接,注册它能做的事情,系统将其吸收:实时的、可发现的、可观测的。答案不会根据你正在添加的能力类型而改变。它不会根据语言改变,也不会根据它是基础设施还是业务逻辑改变,更不会根据是由人还是智能体创建它而改变。添加一个 Worker。
这不仅仅是架构的一致性。这是类别的崩塌。在传统系统中,每种能力都存在于自己的本体论中。队列有代理语义,HTTP 有路由语义,cron 有调度语义,智能体有编排语义。在 iii 中,它们都是同一回事:一个注册 Functions 和 Triggers 的进程。语义存在于 Functions 中,而不存在于基础设施中。
软件中的范式转移不是增加功能。它们崩塌类别。“一切皆文件”使 Unix 可组合。作为函数的组件使 React 的思维模型深入人心。在 iii 中,答案总是“添加一个 Worker”。这就是原语。这就是整个模型。
## 活的系统
因为一切皆是 Worker,所以涌现出了三种传统架构无法产生的属性:
实时发现。当 Worker 连接时,它会接收每个其他 Worker 上注册的每个 Function 的完整目录。当新的 Functions 出现时,每个 Worker 都会收到通知。当 Worker 断开连接时,每个 Worker 都会收到通知。引擎是唯一的真实来源。
对于智能体来说,这也是认知基础设施。智能体可以准确看到整个系统现在能做什么。不存在智能体收到过时上下文的风险。
实时扩展性。向正在运行的 iii 系统添加新的 Workers 和能力,而无需重新部署或重新设计架构。没有配置变更,没有重启,因为系统在运行时扩展。
这正是智能体系统真正想要运行的方式。你永远不需要中断生产来添加新能力。你连接一个新的 Worker,它的 Functions 分布在整个系统中,任何智能体都可以随意使用它们;甚至可以使用它们自己的 Workers 来扩展系统。
实时可观测性。iii 的可观测性建立在 OpenTelemetry 之上。每次 Function 调用都携带一个 trace ID。每次 trigger() 调用都会将其跨 Workers、跨语言、跨队列交接传播。通过 iii Logger 发出的每条日志都会自动关联到活动的 trace 和 span,作为结构化的 OpenTelemetry LogRecords 发出,并路由到你使用的任何后端:iii Console、Grafana、Jaeger、Datadog。这不是一个要安装和集成的独立组件,它只是另一个 Worker。Traces、指标和结构化日志由引擎本身生成,而不是由应用级中间件生成。
当一个智能体调用一个工具,该工具将一条消息入队,进而触发一个下游函数写入状态,整个链条就是一个 trace。不是三个通过时间戳关联或手动跟踪 trace ids 连接的独立系统。一个 trace,跨语言、跨 Workers、跨越智能体-后端边界。你可以直接从缓慢的瀑布式 span 进入解释发生了什么的相关日志。
## 创建 Workers 的智能体
这便是模型真正变得递归的地方。
iii 支持硬件隔离的微 VM Workers,具有硬件隔离特性。沙箱功能本身就是一个 Worker,拥有自己的文件系统、网络栈和进程树。你可以用一条命令创建一个 Worker:iii worker add ./my-worker。沙箱 Worker 连接到引擎,注册 Functions 和 Triggers,并像其他每个 Worker 一样参与系统。
现在考虑一下,当智能体可以做到这一点时会发生什么。
智能体 Worker 也可以在运行时启动一个新的沙箱 Worker。该沙箱获得自己的隔离环境。它注册自己的 Functions 和 Triggers。这些 Functions 立即出现在实时目录中。其他智能体和服务可以调用它们。当不再需要沙箱时,它会断开连接,其 Functions 注销。
沙箱不是一个单独的“沙箱产品”。它是一个 Worker,使用与其他一切相同的原语——只是碰巧提供了硬件隔离。智能体创建沙箱 Worker 只不过是一个 Worker 创建了另一个 Worker。
这就是当基础设施变成设计模式而非产品类别时的样子。需要不受信任代码的隔离执行?那就是沙箱 Worker。需要临时的专家级智能体?启动一个 Worker,注册 Functions,完成后关闭它。需要并行任务执行器集群?让一个 Worker 启动其他 Workers。原语是一样的。模式各异。
## 界限消失
回到马具的辩论。Anthropic 说要薄。LangGraph 说要厚。他们在争论要在模型周围编码多少认知结构。薄与厚的辩论很重要,但这只是设计空间内的问题,而不是关于设计空间本身的问题。
当智能体是 Workers 时,薄与厚只是你注册多少 Functions 以及如何组合它们的问题。薄马具是一个拥有少量 Functions 的智能体 Worker,它让模型决定接下来 trigger() 什么。厚马具是一个拥有更多 Functions、显式批准关卡以及在入队下一步之前的条件逻辑的智能体 Worker。这是相同的原语和系统,只是模式不同。
脚手架的隐喻也随之转变。行业谈论马具脚手架是临时的。随着模型的改进,你会移除它。Manus 描述过四次重写 Claude 的智能体框架,每次重写都是发现了塑造上下文的更好方式的结果。随着新模型吸收了相关能力,Claude Code 剥离了规划步骤。
如果马具由与后端其余部分相同的原语构建,那么移除脚手架仅仅意味着简化一个 Function。你不需要重新架构集成层。你不需要重建两个系统之间的接口。你只需注册更少的 Functions,或者以不同的方式组合它们。
## 任何事物皆 Worker
Worker 是任何可以打开 WebSocket、注册 Function 并讲原语接口的东西。对于那个东西是什么或用什么语言编写,没有任何限制。
iii 附带 TypeScript、Python 和 Rust 的 SDK。但这些并不是系统的边界。它们是一个开放线协议的三种实现:WebSocket 上的 JSON。引擎不知道连接的另一端是什么语言。它看到的只有 fun