架构总览 · 侧载接入模型

AI Agent 算力平台 · 侧载接入

平台内建的角色只有两个:调度机(控制面)和开发机(执行面)。开发人员、测试人员以及其他任何系统都不是容器、也不注册为实例——它们持有一枚侧载令牌(sdl_…),调用路径固定为设备 → 控制面 → 该主机的根调度机 → 开发机。设备只与控制面通信:既不知道调度机地址,也不持有实例凭据;侧载不发心跳、不占租约,权限是一个封闭的 scope 集合。

English
接入侧:一台设备 + 一枚侧载令牌 —— 无容器、不注册实例 开发人员 sdl_… · tasks:write · console:run 测试人员 sdl_… · tasks:write · tasks:read 其他系统 CI · 脚本 · 客户端 · 按需授权 HTTPS · Authorization: Bearer sdl_… 调度机 · 控制面(内建) 任务状态的唯一来源 · 令牌到此为止 侧载入口(PCS) 校验 Bearer sdl_ · 检查 scope 解析该主机的根调度机 任务 API 提交 · 查询 · 取消 · 重试 幂等键 · trace_id 队列与派发 Pull + 心跳 · 能力匹配 优先级 · 老化 · 重试 状态机 · 租约 QUEUED → ASSIGNED → DONE 租约 · fencing token 文件通道 附件进 · 产物出 SHA-256 · 逐字节一致 PostgreSQL · 审计 任务状态的唯一来源 令牌创建 / 删除留痕 任务派发 · Pull 心跳 · 事件 · 产物 开发机 · 执行面(内建 ×N) 只有本主机的调度机会向它派发任务 任务工作区 每个任务独立目录 源码树 · 输入附件 Agent 执行 运行绑定的 Agent Profile 或确定性 Shell 控制台命令 task.console.* 需要 console:run 资源准入 启动前检查预算 超限则继续排队 扩容即增加开发机 —— 其他所有参与者都走侧载路径。 侧载边界 不是内建角色 · 无容器 · 无实例记录 · 不发心跳 · 不占租约 · 不知道调度机地址 · 不持有实例凭据 · 令牌以哈希形式保存 (明文只显示一次) 权限 scope(封闭集合) tasks:read tasks:write builders:read queue:read console:run console:run 需在 tasks:write 之上额外授予 生命周期与审计 · 最近调用 ≤ 120 s 视为在线 · 过期或删除 → 下次请求 403 · 创建 / 删除均记入审计 · 令牌与哈希不写日志 与后台管理端走同一条 任务 / 租约 / fencing 路径。
图例 侧载接入(设备 → 控制面) 任务派发 / 心跳(Pull) 文件通道(附件 / 产物) 侧载边界(外部系统)
① 持有侧载令牌→ ② 提交任务→ ③ 校验 scope→ ④ 调度机排队 / 派发→ ⑤ 开发机执行→ ⑥ 收集文件→ ⑦ 客户端下载产物

只有两个内建角色

平台内部只有调度机和至少一台开发机。开发人员、测试人员、CI 任务、脚本和其他任何系统都通过侧载令牌进入,因此不需要为它们新增角色、容器或派发规则。

固定的调用链

设备只调用控制面;控制面校验令牌、解析主机及其根调度机,再转发请求。设备看不到调度机地址,也不持有实例凭据——与后台管理端走的是同一条路径。

侧载不是 Agent

它没有容器、不注册、不发心跳、不占租约。由于复用常规的任务 / 租约 / fencing 路径,它无法绕过调度、准入和审计。

权限天生最小

scope 是封闭集合:tasks:read、tasks:write、builders:read、queue:read、console:run。其中 console:run 需在 tasks:write 之上单独授予;未知 scope 会被拒绝而不是忽略。

文件双向流动

输入以附件形式上传,落到 <workspace>/attachments/。输出通过调度机的产物通道收集:任意格式,校验 SHA-256,逐字节返回,而不是塞进标准输出。

生命周期与审计

last_seen_at 最多每 30 秒更新一次,令牌在 120 秒内视为在线。删除或过期在下次请求时以 403 生效。令牌的创建与删除进入审计;令牌及其哈希永不写日志。