迭代
迭代的SDKcore/os):上下文api、流处理器、响应式客户端、React
绑定和OAuth应用会话 iterate/*. 包在此导出源代码
工作空间,并在打包时使用声明编译JavaScript。它的 iterate bin是命令行
(src / cli / README.md).
SDK/平台行
SDK保存了用户代码运行或说话的内容,平台是它的第一个用户:核心/操作系统构建
自己的实体 iterate/sdk,第一方应用程序的代码只使用 iterate/*. 每一个
子路径的 package.json的 exports 是一个公共模块;没有其他东西是可进口的。
- 当用户代码运行或调用它时,模块就属于这里:一个加载的worker、一个facet、一个 处理器、浏览器或Node客户端,或者它们和平台之间的有线契约。它 当只有平台的Worker运行时,它属于core/os;当有更多的Worker运行时,它属于packages/shared 一个应用程序需要它,而用户代码永远不需要。
- 在core/os之外,没有任何包和应用程序导入core/os。
import-js/no-restricted-paths在.oxlintrc.json解析下的每个导入packages/**和internal-packages/**到一个文件,所以键入 进口,再出口,动态import()以及之后添加的应用程序lint/oxlintrc-platform-line.test.ts别针。该平台的测试在test/,这 该规则没有涵盖:它们可以导入core/os,而core/os只保留简单的单元测试。 - 没有私有的核心包背后一个薄
iterate: core/os将会导入模块的用户代码 不能,而且SDK的类型无论如何都必须被捆绑或发布。
决定的原因,以及如何工作,代理SDK,凸,
Supabase、tRPC、Hono和Wrangler划一条线:决策记录
docs/2026-09-24-sdk-platform-line.md 在迭代/迭代# 3018。
每个符号一条路径
平台为项目加载的代码(重写规则背后的配置入口点、面、工作器) 从一个路径中导入每个符号,然后加载器将其链接到这个部署自己的构建中:
| 路径 | 它包含什么 |
|---|---|
iterate/sdk | 工人主持: IterateConfigEntrypoint, FacetDurableObject, StreamProcessorDurableObject,它们的类型,以及capnweb的构造函数 |
iterate/stream/processor | 处理机表面: StreamProcessor, defineProcessorContract, LiveState,事件和合同类型。它也在Node中运行 |
iterate/stream/contract | defineProcessorContract 单是合同类型 iterate/stream/processor 再出口。浏览器加载的代码会导入引擎需要的代码 node:async_hooks |
iterate/email | 这个 email 方面的合同, email/received 和 email/sent 还有它们折叠成的线 |
iterate/agents | 代理应用程序的两个类,其中一个是配置 agents.ts 再出口; iterate/agents/install 安装它(其自述) |
zod | 每个隔离一个副本,所以SDK检查的是模式用户代码生成的那个 |
import { StreamProcessorDurableObject } from "iterate/sdk";
import { StreamProcessor, defineProcessorContract } from "iterate/stream/processor";
import { z } from "zod";
const Contract = defineProcessorContract({
slug: "hoarder",
version: "1.0.0",
description: "keeps every blob it reduces",
stateSchema: z.object({ blobs: z.array(z.string()).default([]) }),
consumes: ["blob"],
emits: [],
});
class Hoarder extends StreamProcessor {
contract = Contract;
reduce({ event, state }) {
return { blobs: [...state.blobs, event.payload.blob] };
}
}
export class HoarderDurableObject extends StreamProcessorDurableObject {
processor = new Hoarder();
}
从加载的代码到达上下文
加载的代码到达它的上下文 using itx = this.getItx():当块结束时,作用域;
通过它发出的每个调用和它等待的每个句柄都被释放。每一个加载
WorkerEntrypoint 有 getItx, SDK的主机(IterateConfigEntrypoint,
FacetDurableObject, StreamProcessorDurableObject).
import { IterateConfigEntrypoint } from "iterate/sdk";
export default class extends IterateConfigEntrypoint {
async fetch() {
using itx = this.getItx();
const { projectSlug } = await itx.whoami();
return new Response(`Homepage of ${projectSlug}`);
}
}
将每个作用域与 using 在保存调用的最小块中,等待内部的每个调用
它,和手数据,而不是句柄,从它:一个保留作用域,步骤或句柄保留上下文,和任何
Facet持有它,在项目闲置后驻留。需要到达的对象取An
访问器(() => this.getItx()),而不是作用域;调用之后的工作在a下运行
处理器的 runInBackground 索赔。棉绒拒绝生毛 ITX.get() 和一个 getItx() 否 using
绑定在此存储库中(iterate/no-raw-itx-get).
配置工作者的 getItx() 根的作用域是谁调用它:每个上下文链接到
根继承根的规则,所以脚本在 /child 调用 itx.voice.setupVoiceAgent(…)
作为根运行worker。在任何方法中 fetch, this.callerPath() 命名上下文
电话来自(/child),因为平台标记了它,所以worker为调用者创建了什么
不能比呼叫者拥有更多的东西;
async setupVoiceAgent({ streamPath }) {
using itx = this.getItx();
await itx.agents.at(this.callerPath()).create(streamPath); // linked to the caller, not to `/`
}
谁写了一个事件
项目中的任何人都可以将任何事件附加到任何上下文,并且每个事件都说明它来自何处。
月台邮票 source.origin,代码或会话编写它的上下文
source.principal (成员)及 source.platform. 作家自己的价值观被抛弃了。一个
一个作家所拥有的领域 source.processor,即处理器为其编写的引擎标签。
// A script in /agents/a:
await itx.cd("/agents/b").append({
type: "events.iterate.com/agent/context-added",
payload: { role: "user", content: "hello" },
});
// Stamped { origin: "/agents/a" }. Agent b's model reads "[from /agents/a] hello",
// and the same from `itx.agents.get("/agents/b").message("hello")`.
单词 / (一个成员的会话,破折号)作为一个人的,没有发送者。 origin 是
运行代码的上下文,而不是谁要求它运行的上下文(core/os) caller.ts stampCaller 说为什么会这样
这是建议)。批写道: append(...events) 是一个提交,不管它包含多少事件。
失控反应自行停止:代码对其他代码的输出进行一轮又一轮的反应
两个代理相互应答,一个处理程序附加它所处理的内容,在几个之后停止
以一个错误命名开始反应链,上下文记录一个
itx/loop-limit 的事实。阅读仍然有效。
例外是监狱,一个带有裸的上下文 itx ⇒ null 行加上它旁边的授予权。一个光秃秃的
监狱是双向封闭的:它的代码没有附加到任何地方,也没有代码附加到它里面。赋予其
itx.append 每个上下文都可以附加到它里面;它自己的代码仍然不能解除null。一个
Jail限制的是运行在其中的代码,而不是它通过授权创建的上下文。
测试处理器
iterate/stream/test-support (Node)是SDK自己的引擎测试使用的线束:
import { reduceProcessor } from "iterate/stream/test-support";
// test/helpers/presence/processor.test.ts: durable ticks are reduced, ephemeral pokes are not
const state = reduceProcessor(new PresenceProcessor(), [{ type: "tick" }, { type: "poke" }]);
// state.ticks === 1
memoryStream, memoryStorage 和 settle 全程驾驶 ProcessorEngine 针对一个
内存日志(src/stream/processor.test.ts 显示)。
多步跑
其工作执行步骤(运行部署、创建项目)的处理器仍然是一个
契约,一个国家,一个 reduce 和一个 processEvent. 核心/ os src/deployment/ 工作顺利吗?
的例子。
- 这个要求是事实。 动词追加
<x>-requested然后回答它的偏移量,也就是 运行的id。fact保存运行所需的每个输入,或者命名facet为其保存的内容 它自己的存储(部署的配置):下一个化身读取日志和存储, 从来没有电话。 - 每一步都有一个事实,由请求的偏移量和尝试次数来指定
(
<slug>/<fact>:<requestOffset>:<attempt>),所以重复的追加操作只着陆一次。键控有效载荷 不保存值为的键undefined:流将重复的追加对象与存储的追加对象进行比较 JSON,它没有这样的键。 - 州政府说了跑路的位置: 请求、尝试、最后一步和偏移量
它的事实。
snapshot()显示它,页面可以实时观看它。 processEvent来自国家的预付款。 在delivery.caughtUp没有工作的跑步 这个化身的驱动器只有一个司机runInBackground. 司机的索赔使 Facet,如果Facet死亡。中途发现的跑步失去了它的化身,或者它的司机自己的事实 没有着陆,所以司机立即开始下一次尝试:如果出了什么问题,工作是 再次运行。- 重复每一步都是安全的。 一次尝试从第一步重新开始,每一步都有效果
接受自己早期的成功。事实记录进步;他们不会跳过那些与死亡相伴的工作
化身(
/tmp(开放式计划)。 - 尝试是以事实计算的,并且是有限制的。 一个
attempt-started事实摆在每个人面前 尝试。无论结果如何,结束的尝试立即解决了运行:没有重试等待 在处理器内部,调用者再次运行它。只有一次尝试在解决问题之前就结束了 Run(游戏邦注:它的化身已经死亡)将在引擎的5次下再次运行 死亡,这样一场尝试都失败了的赛跑还能复活下来。 - 最后一个事实是,
<x>-settled { requestOffset, status, error? },按要求输入。它的status是四个中的一个:succeeded;refused,预期结果;failed,任何其他错误 (再运行一次);unavailable,平台故障(再次运行)。未打开运行的请求被解决refused当它被处理时,在blockProcessorWhile:没有状态会记住请求,所以没有 过一会儿就可以解决了。来电者接以下语句waitForEvent({ type, payload: { requestOffset }, afterOffset })。 - 测试它作为表: 事件通过
reduceProcessor,通过processEvent带着假的 对假时钟的影响,和一个重减行。
节点连接
iterate/node 为Iterate脚本和live公开一个连接所有者
提供者。它使用与CLI相同的协议和清理:
import { connectIterate } from "iterate/node";
using connection = await connectIterate({
baseUrl: "https://os.iterate.com",
auth: { type: "bearer", token: process.env.ITERATE_BEARER_TOKEN },
});
using project = await connection.session.projects.get("my-project");
console.log(await project.run("async (itx) => await itx.whoami()"));
事件类型
平台事件类型为 events.iterate.com/<namespace>/<event>:一个命名空间段和一个
事件段,都是小写的kebab-case,没有第三段。
下面的每种类型 events.iterate.com/ 遵循这些规则,包括测试类型。没有的类型
该前缀属于附加它的人,并且对平台不透明:测试使用类似的类型
demo/ping 并且一个项目可以使用它自己的域(events.garple.com/sales/…).
名称空间
itx保存上下文引擎自己的事件:核心契约的一切 (core/os/src/stream/core-processor.ts)减少,验证或拒绝,加上记录 流、上下文持久对象和SDK处理器主机自己编写。其中模式 reduce live决定它,而不是哪个上下文持有事件:fetch路由和顶点 入口目标是核心状态,所以它们是itx即使只读取项目根目录的副本。 域处理器可以使用一个itx事件(代理使用)itx/run-*、项目 处理器itx/ingress-configured); 它在它的processorDeps而不是 定义事件本身。核心的检查点是core:它是一个存储键,而不是类型 前缀。- 域命名空间 事件日志所属的上下文类型的单数名称
To,当有一个时,它是定义合同的段格:
account,organization,project,repo,workspace,sandbox,deployment,secret,agent,voice-agent. 一个事实 交叉发布到另一个日志保留自己的命名空间:repo/created在/仍然是回购事实。 - 一个集成 例如,使用自己的名称作为名称空间
chrome,slack,google,github。 test保存仅测试追加的类型。产品代码永远不匹配test/*类型。一个 测试契约可以保留自己的段(counter),但它的事件却悄无声息test/. 测试必须 不要为不存在的类型借用生产名称空间。
事件名称
- 事实是过去时:
<object>-<verb-ed>,或裸露的<verb-ed>当对象是 命名空间本身的主题(itx/created是上下文,agent/paused是代理)。的对象 出现在前面并且是单数。 - 把单词拼出来。 不允许使用缩略词(
spk); 一个真正的词是(mic), an也是如此 API的首字母缩写是(llm,rpc,itx). - 提问和回答。
<x>-requested请求,其偏移量标识请求。答案 取三种形状之一:<x>-settled是请求程序读取结果时的一个终端事实。它的名字requestOffset并携带结果:成功、失败或取消、状态或错误。 例子:itx/run-*,agent/llm-request-*,project/hostname-add-*,deployment/run-*。<verb-ed>或<verb>-failed当成功是其他日志等待的事实时使用,例如 证书:create-requested→created或create-failed,delete-requested→deleted,hostname-remove-requested→hostname-removed. 重试失败,而不是失败 报告没有-failed的事实。- 这个答案本身就是一个事实,它的答案就是提问者的答案:助手的答案
agent/context-added带着llmRequestOffset的llm-request-requested它的答案。
- 一种变化对应一组动词:
added/removed对于集合中的成员:organization/member-added,organization/project-added主机名。created/deleted对于具有生命周期的实体:项目、仓库、工作区、沙盒、 部署,代理。set/deleted对于键值:secret/*。set/cancelled对于时间表:itx/schedule-*. 每次出现都是fired或failed。-configured用于设置一行或清除一行的一个事实null(itx/subscription-configured,itx/rewrite-rule-configured,itx/fetch-route-configured), 设置一个单例(itx/ingress-configured),或者合并部分配置 (agent/configured:省略的键保留其值)。
- 平台自己做的事情 是关于物体的简单事实:
itx/schedule-fired,itx/schedule-failed,itx/subscription-delivery-halted。 - 短暂的事件。 记录发生的事情的短暂事件与其他事件一样被命名
事实:
itx/rpc-stub-attached,itx/live-state-changed,chrome/navigated. 可能有三种 单数名词:直播流的一个序列片段是a<stream>-frame(voice-agent/mic-frame,agent/llm-response-frame),心跳(voice-agent/keepalive), 诊断记录(itx/alarm-trace). 持久事件从来不是名词。 - 家庭和前缀。 代码通过前缀匹配某些家族:
…/itx/run-,…/itx/subscription-,…/itx/schedule-,…/project/hostname-. 在命名新类型之前,请进行检查 它加入这些家庭不是偶然的。从来没有匹配…/itx/作为一个整体:它不是 权限边界,它捕获实时状态delta、存根存在和子节点 公告。 - 代码遵循类型。 从类型构建的常量、模式、测试fixture或等幂键
它的名字(
itx/child-created:<path>). 更广泛的概念、模块和工人日志 事件名称保持他们的:流,计划追加,core,scheduled-append.completed。 - 重命名。 重新命名必须符合这些规则之一,而不是口味。类之外存储类型 平台的持久对象(设备固件、发布的SDK、项目的配置repo); 仅在迁移该存储的更改中重命名它。
| 名称空间 | 中定义的 |
|---|---|
itx | core/os/src/stream/core-processor.ts (及其叶事件目录), stream.ts, scheduled-appends.ts, subscription-delivery.ts, core/os/src/context/built-ins.ts, core/os/src/fetch-routes.ts, core/os/src/iterate-context-durable-object.ts, core/lib/src/stream/{run,processor}.ts |
account, organization, project, repo, workspace, sandbox, deployment, secret | core/os/src/<name>/contract.ts (repo,工作区,沙盒和部署也使用 project/entity-lifecycle.ts) |
agent | core/lib/src/agents/contract.ts |
voice-agent | packages/voice/src/voice-agent.ts, packages/voice/src/events.ts |
chrome | packages/browser-extension/public/panel.js |
email | core/lib/src/email.ts |
integration | core/lib/src/integrations.ts (项目的包写入的注册表;它的文件夹是 core/os/src/integrations/registry.ts) |
test | 只测试 |
note/added 只是Agents composer中的一个例子;没有合同定义 note。