与 Circle 的集成

已被取代(2026-10-21):hub 扩展 Circle 菜单、/v1 客户端接口和同步模型已随旧服务器退役。现在 Circle 把 Hub 当作普通 MCP 服务器连接,Circle 里不出现 Hub 专用的界面或标签;运行记录、知识库、任务包和系统管理放在 Hub 的只读控制台(规划中)。见通用模型的 D06;本文只作记录保留。

hub 是 Circle 的增强,不是另一个首页

Circle 是你自己跑的 agent;hub 是团队跑的那个进程。同一套产品,两种运行方式:circle 和 circle hub。管理员也用 Circle,只是角色不同,没有第二个管理程序;无图形主机用 hub 自带的命令行。

为什么仍要一个常驻进程:独立审批需要共享状态;任务和证据要活过会话和机器;约束 agent 的东西不能跑在 agent 进程里。

菜单:扩展,不并排

内部标准里的 B/C/D/E 不进用户菜单。用户眼里技能就是技能、工具就是工具,不分本机和 hub。连上 hub 后,Circle 的菜单项被扩展:

Circle 菜单 连上 hub 后 对应标准
sessions 工作:本机会话 + hub 任务混排,带状态;「需要我」的计数(待我审批 / 待我回应 / 异常) C 工作
skills 本机技能 + hub 技能包,同一张表,一列「来源」;页内分页签:技能包 / 产品线模块 / 规则 E
tools · mcp servers 本机工具 + hub 工具目录(srv_*),同一张表 B 的连接器、D 的资源工具
approvals 本机许可 + hub 审批请求,页内两个页签,不混算 C 审批
新增 · 资料 来源、原件、声明、快照;只在登录 hub 后出现 B
新增 · 资源 网关、worker、操作、现场、未知结果;只在登录后出现 D
settings › hub 登录、范围、人员、策略、worker、通知 C 身份与策略

字母 B/C/D/E 只留在标准文档和登记簿页面的「对象标准」小标签里。

同步模型

Circle 本地不存任何 B/C/D/E;看到的永远是 hub 按当前身份算出的投影。

  • 拉:每个列表都是 hub 查询(带 ETag);任务事件按游标补读,断线重连从游标继续,不丢。
  • 推:hub 对「需要我」的事发通知流(审批请求、待回应、异常),Circle 订阅,断了退回轮询。
  • 卡片是投影:会话里的审批卡、报错卡对应 hub 的记录;点「批准」前先重读一次,别人已处理的卡片变成「已由某人处理」,不会双批。
  • 状态行:「hub · 12 秒前同步 / 离线」;离线时任务在 hub 继续跑,重连刷新。
  • 只装 Circle、不登录 hub:没有这些入口,Circle 就是通用 harness。登录了只看到自己范围内的东西,计数在服务端按身份算。
  • 一个 Circle 一次连一个 hub(第一版)。

会话(L0)长什么样

左栏:「需要我」的计数、「工作」清单(本机会话和 hub 任务混排)、Circle 原有菜单(含扩展的条目)。中间:会话;hub 的受理、证据、过闸、平台核验以事件行回来;需要人的事是内联卡片——报错卡(错误码 + 事实 + 申请人的选择)、审批卡(变更集 + 绑定三项 + 资格 + 批准 / 驳回)。右侧旁栏:这个任务的阶段、绑定、证据、产物。底部:输入框和状态行。

接口的共同形状

  • 除二进制下载,回应一律 JSON 信封:成功 data、失败 error(code / category / message / details / retryable);登录以外的每个接口要 Bearer。普通请求体上限 64 KiB,上传原件上限 16 MiB。凭据无效或过期是 401 UNAUTHENTICATED,范围拒绝是 403 FORBIDDEN_SCOPE。
  • 客户端对任何轮询遇到的 401 只提示登录过期并要求显式重新登录,不自动弹密码框;run token 不由客户端续期。
  • 写操作的受理是幂等的:idempotencyKey 8–200 字符,分区含租户、范围、主体、动作,在线窗口二十四小时;同键同内容返回原受理(deduplicated:true),同键不同内容是 409 IDEMPOTENCY_MISMATCH。重试必须保留原键与原内容;要改内容用新键。受理事务同时落下任务、幂等记录、outbox 与唤醒。
  • 同范围、同源字节、同方法与契约版本的产出复用同一制品 id。

任务与审批的读取投影

  • 任务列表与任务详情共用同一 TaskView 投影:任务身份、租户/范围/主体、标题、phase、当前输入修订、输入、修订、流程版本、待回应列表;绑定了能力包的另带包 id 与版本。待回应列表含历史回应项,客户端只提示 status=open 且修订等于当前修订的项。
  • 列表先按当前身份的租户与授权范围过滤,再按创建时间倒序、同刻按任务 id 倒序截取;它包含范围内其他人创建的可读任务,不按创建者缩小。没有可读任务或没有授权范围返回空数组;没有读取权限是 403 FORBIDDEN_ACTION。limit 默认 20、允许 1–100;非法的 limit 是 400 INVALID_PAYLOAD,不静默截断。
  • 审批列表是 ApprovalView:approvalId、任务、输入修订、申请人、目标身份、动作摘要、动作、状态、决定人、到期时间;按创建时间与 id 升序返回全部记录,包括旧修订的;未决定时决定人为 null。旧修订上的 approved 不证明当前动作仍获授权,执行出口仍核对修订、目标身份、动作摘要与有效期。读取审批只需要 tasks.read,不授予回应审批或操作资源的权限;读取不改变审批状态,不生成决定或事件。

事件补读

  • 任务事件按 seq 升序分页,每页最多 500 条,可带 waitMs 长轮询(0–30 秒);回应带本页与最新游标 after。客户端为每个任务单独保存已处理游标:消费成功后推进到最后一条的 seq,空页保留原游标,重连后用原游标补读。seq 允许跨任务的间隔,不要求连续。
  • 客户端断开或终端关闭不触发取消;取消只有显式调用,且只是一个请求,不证明远端已停止。

通知流与工作队列

  • 通知是持久 outbox 的投影:分页查询带 items / cursor / hasMore(limit 1–200),SSE 流用同一游标或 Last-Event-ID 续读;通知事件带 id=seq,另有游标事件给出下一游标,即使本页没有通知。过滤掉当前身份不可见的事件之后才分页,游标可以越过被过滤的序号。服务端每轮重核身份与角色:会话过期发 session.expired、撤权发 permission.revoked、内部错误发 stream.error,然后关闭;不在流中索取密码。SSE 连接按租户/主体有上限(默认四条),超限 NOTIFICATION_STREAM_LIMIT,断开释放名额。
  • 保留窗口有限:默认至少保留最近 10000 条事件、最多 100000 行,年龄目标七天(硬上限优先)。窗口之前已有事件被裁剪时,首次响应的 replayWindow.minCursor 大于零,客户端不得把它当完整历史;过窗的旧游标是 CURSOR_EXPIRED(details 带 minCursor),随后从 cursor=0 重拉保留窗口、按 seq 去重,并用当前状态查询补窗口外的任务与审批现状;历史正文从 B 的原件取,不从当前状态反推过去顺序。任务事件补读用同一窗口纪律。窗口不按主体的已读水位保留:全局其他范围的事件产出也缩短本人的窗口,离线几天不保证能补全;在线跟随优先,轮询一页的总预算不超过「窗口条数 /(4 × 全局峰值事件速率)」这类约束与年龄上限四分之一的下限,没掌握峰值时从短间隔起步,并以 minCursor 或游标缺口触发状态重建。
  • 工作队列五个桶:待我审批 / 待我回应 / 我提交的 / 我负责的 / 异常,各带 count、oldestWaitMs、overSla、slaMs。审批计数按当前管理员资格、两人规则与申请人过滤;输入与 bot 失败只归所有者;私有任务不借队列暴露。异常含 unknown/failed、过期或被取代的请求、失败的 bot 受理与离线的 worker,分组各给计数,分面不适用的组明示为省略,不当作全平台确为零。SLA 阈值在响应里明示(默认审批二十四小时、输入五分钟、其它一小时);「我负责的」只读持久的分派记录,不从模型文本或申请人身份推断指派。worker 离线由心跳阈值(默认九十秒)决定,与异常的 SLA 相互独立,不按业务结果猜离线。
  • 分派:任务可读且持输入权限的所有者或该范围的管理员可设置 assignee 与 dueAt,成员不能改别人的任务;assignee 必须是同范围的活跃成员或管理员;分派不授予私有任务的读取权限,私有任务只能分给所有者。dueAt 是队列 SLA 日期,与执行截止时间独立;范围策略可经单项变更集分别调整五个桶的 slaMs,计数优先用任务自己的 dueAt,范围值不一致时 slaMs 为 null 并给出逐范围依据。

事件的正文投影

  • 任务事件、管理事件与通知对同一事件用同一固定字段投影:状态/原因码、规则版本与结论、fence、工具名、运行绑定等元数据内联;结果、工具输出、模型文本、调用参数与错误详情的正文不内联。
  • 有任务身份的事件另带 content:{sha256,size,downloadPath,retained?}:正文按需在内容路径回读原字节,响应头摘要与 content.sha256 对应,权限沿用任务的可见性;retained 是正文已有的 B 引用列表(各自的 id、SHA、下载路径),引用自己的 SHA,不与 content.sha256 混用。正文只在保留窗口内可读,过窗为 CURSOR_EXPIRED;retained 引用的原件按 B 的现行权限可读,不随窗口删除。
  • 通知只含 id、状态/原因码与证据引用;完整结果从任务结果接口取。规范事件包括审批的请求/决定/被取代、任务的待审/待输入/交付/失败/取消、操作未知、bot 的启动/输出/受理失败,以及变更集的状态;旧名继续留在任务事件的兼容流里,通知目录用规范名字。输入改写与新变更集都会发被取代事件,并给出取代者。

工具目录与调用

  • 目录按当前身份计算:权限、范围、资产读权限、个人凭据权限与已批准运行中的当前资产修订逐项过滤;停用、修订或撤销权限立即影响后续目录与调用。工具名只用小写字母、数字、点和下划线,租户内唯一,多实例用别名;同名登记被拒绝(TOOL_NAME_CONFLICT)。
  • 目录响应带 ETag 与 Vary: Authorization:同一身份未变化时 304(无 body,不要解析),变化时 200 整体替换;目录缓存不跨身份共用。工具的输入 schema 以目录实际返回为准,客户端不维护第二份。
  • 调用带 8–200 字符的幂等键:相同身份/范围/工具/键与规范化输入复用原任务与结果;同键不同内容是 IDEMPOTENCY_MISMATCH。同步读调用也由已认证 worker 执行,平台保存任务、输入修订、操作、租约与 fence,资料侧核对字节 SHA 后才返回。同步等待超时给 UPSTREAM_TIMEOUT,details 带任务 id 与 remoteStoppingVerified:false——这不是取消;要改参数就发新调用,工具任务的输入修订不可变。失败收据仍可按原键重读;一次新的查询用新键。
  • 工具调用的任务与事件初始归调用者个人(见角色与范围):范围内其他人也不能经任务接口读到其查询与候选内容。失败分类的收敛见审批与执行链。
  • 实例别名的解析顺序固定:产品线清单的默认前缀、包前缀,最后以包 id(连字符改下划线)作前缀;显式别名优先。没有安装或声明的产品线不获得前缀;移除产品线并重启后,它明确绑定的工具从目录隐藏,恢复安装后可继续使用原已批准的绑定。

触发与 bot

  • 触发器与 bot 都是草稿资产,经独立批准、由主人激活后才运行:定时触发(间隔至少一分钟)或连接器事件触发;组件须同范围,技能清单须允许该触发方式。bot 的运行身份就是登记者本人,结果渠道是 Circle 事件流;渠道注册可扩展。
  • 错过的定时 occurrence 合并成最近一次到期运行,不逐条补跑;每次运行的 trigger 记录 {kind, dueAt, firstDueAt, skippedOccurrences},游标严格推进到未来。受理失败是一次独立回滚:runs 里 status=failed、taskId=null、error 带分类(不带来源细节),事件游标继续前进,不伪造任务、不伪造 bot.output。已拒绝的事件不重复尝试,后续新事件照常受理。
  • 为避免自触发,平台忽略任何 bot 运行产生的 source.changed 观察;显式登记的连接器事件与人工读取照常触发。连接器事件按同源/范围/外部 id 去重,同 id 不同内容拒绝。

本文是 circle-hub 的设计规范之一,按 CC BY 4.0 授权:引用、借鉴须署名 Circle Hub 与 qingshanfeihu 并注明原始出处链接。见设计规范索引。