Skip to content

Latest commit

 

History

History
492 lines (398 loc) · 17.6 KB

File metadata and controls

492 lines (398 loc) · 17.6 KB

Runtime Agent 通用接入规范

本文定义任意新 runtime 接入 ClawManager Agent Control Plane 的通用方案。后续新增 OpenClaw、Hermes 以外的 runtime 时,应优先遵守本文,再补充该 runtime 自己的镜像构建细节。

接入目标

每个可托管 runtime 镜像内都应包含一个常驻 agent。Agent 不需要暴露端口,只需要向 ClawManager 发起出站 HTTP 请求,并完成:

  • 注册和 session token 续期。
  • 心跳和在线状态上报。
  • 运行时状态、CPU、内存、磁盘、网络、健康信息上报。
  • skill inventory 同步和 skill 包上传。
  • 平台命令轮询、执行、完成结果回传。
  • 使用 ClawManager 注入的 AI Gateway 环境变量访问 LLM。

Agent 应把 runtime 自身名称写入 host_info.runtime_typesummary.runtime_typesystem_info.runtime_type。当前 v1 协议里仍保留 openclaw_statusopenclaw_pidopenclaw_version 这几个历史字段名,新 runtime 在协议升级前需要复用这些字段承载自己的主进程状态、PID 和版本。

平台侧新增 runtime type 检查清单

新增 runtime 不只是做镜像。ClawManager 平台侧至少需要完成下面几项:

模块 必做事项
数据库 instances.type 枚举迁移中加入新的 runtime_type
后端 runtime 支持 在 runtime 类型校验、镜像解析、默认端口、持久化目录、代理路径逻辑中加入新类型
后端托管能力 supportsManagedRuntimeIntegration 加入新类型,否则不会注入 Agent 和 AI Gateway 环境变量,注册也会被拒绝
Agent 注册 确认注册 allowlist、错误映射、测试覆盖都包含新类型
Runtime Image Cards 在系统镜像设置的支持类型、默认镜像、默认启用项中加入新类型
创建实例页 在前端 INSTANCE_TYPES、类型文案、图标、默认 env 模板中加入新类型
图标 提供 runtime 官方图标或明确授权图标,放入 frontend/public 或现有资产路径
测试 覆盖 env 注入、agent 注册、runtime 状态上报、创建实例表单、系统镜像卡片

如果新 runtime 采用 Webtop/KasmVNC 基础镜像,默认约定通常是:

  • 桌面端口:3001
  • 持久化目录:/config
  • 代理路径:由 ClawManager 写入 SUBFOLDER=/api/v1/instances/{instance_id}/proxy/

如果新 runtime 不基于 Webtop,必须在平台侧明确它的服务端口、健康检查、代理路径、持久化目录和用户数据目录,不能让镜像和平台各自猜测。

镜像和 Agent 运行要求

Runtime 镜像必须满足:

  • Agent 随容器启动自动运行,例如 systemd、s6 overlay、supervisord 或入口脚本。
  • Agent 不占用 runtime 的业务端口或桌面端口。
  • Agent 通过环境变量读取所有 ClawManager 配置,不写死地址、实例 ID、token、路径。
  • Agent 的本地状态必须写入持久化目录,例如 ${CLAWMANAGER_AGENT_PERSISTENT_DIR}/<runtime>-agent/
  • Agent 日志中不能打印 bootstrap token、session token、AI Gateway API key。
  • Agent 可以在 ClawManager 暂时不可达时退避重试,不应退出导致容器不可用。

建议本地状态目录:

${CLAWMANAGER_AGENT_PERSISTENT_DIR}/<runtime>-agent/
  session.json
  state.json
  logs/
  cache/

ClawManager 注入的环境变量

Agent 控制面

变量 说明
CLAWMANAGER_AGENT_ENABLED true 时启动 agent
CLAWMANAGER_AGENT_BASE_URL ClawManager API 根地址,不带 /api/v1/agent 后缀
CLAWMANAGER_AGENT_BOOTSTRAP_TOKEN 首次注册使用的一次性 bootstrap token
CLAWMANAGER_AGENT_INSTANCE_ID 当前实例 ID
CLAWMANAGER_AGENT_PROTOCOL_VERSION 当前为 v1
CLAWMANAGER_AGENT_PERSISTENT_DIR 当前实例持久化目录
CLAWMANAGER_AGENT_DISK_LIMIT_BYTES 实例磁盘配额字节数

Agent 启动时如果 CLAWMANAGER_AGENT_ENABLED 不是 true,应进入空闲状态或直接不启动控制面逻辑。

AI Gateway

支持托管 runtime 的实例会被注入 OpenAI-compatible 网关变量:

变量 说明
CLAWMANAGER_LLM_BASE_URL ClawManager AI Gateway OpenAI-compatible base URL
CLAWMANAGER_LLM_API_KEY 当前实例专属 Gateway API key
CLAWMANAGER_LLM_MODEL 平台注入的模型目录 JSON,首项通常包含 auto
CLAWMANAGER_LLM_PROVIDER 当前为 openai-compatible
CLAWMANAGER_INSTANCE_TOKEN 当前实例 token,和 Gateway API key 同源
OPENAI_BASE_URL OpenAI SDK 兼容别名
OPENAI_API_BASE OpenAI SDK 兼容别名
OPENAI_API_KEY OpenAI SDK 兼容别名
OPENAI_MODEL 默认模型,通常为 auto

Runtime 内的应用和 agent 如果需要调用模型,优先使用这些变量,不要让用户在镜像内手工写入 provider key。

Agent 生命周期

推荐主循环:

  1. 读取环境变量,确认 agent 已启用。
  2. 生成稳定 agent_id,建议格式为 <runtime_type>-<instance_id>-main
  3. 从持久化目录读取 session token;没有 token 时用 bootstrap token 注册。
  4. 发送一次完整 state report。
  5. 按注册响应里的 heartbeat_interval_seconds 发送 heartbeat。
  6. 按注册响应里的 command_poll_interval_seconds 拉取命令;heartbeat 返回 has_pending_command=true 时立即拉取。
  7. 按 5 到 10 秒间隔采样并发送 state report。
  8. 定期同步 skill inventory,或在 skill 变化后立即同步。
  9. 接口返回 401、session 过期或本地 token 丢失时,重新注册。

注册响应里的 session token 当前为滚动续期。每次成功 heartbeat 都会延长有效期。Agent 仍应能处理 401 并自动重新注册。

API 约定

以下 {base} 都表示 CLAWMANAGER_AGENT_BASE_URL,认证头均为:

Authorization: Bearer <token>
Content-Type: application/json

注册

POST {base}/api/v1/agent/register
Authorization: Bearer {CLAWMANAGER_AGENT_BOOTSTRAP_TOKEN}
{
  "instance_id": 123,
  "agent_id": "myruntime-123-main",
  "agent_version": "0.1.0",
  "protocol_version": "v1",
  "capabilities": [
    "runtime.status",
    "runtime.health",
    "metrics.report",
    "skills.inventory",
    "skills.upload",
    "commands.poll",
    "llm.gateway"
  ],
  "host_info": {
    "runtime_type": "myruntime",
    "runtime_name": "My Runtime",
    "image": "registry.example.com/myruntime:latest",
    "desktop_base": "webtop",
    "persistent_dir": "/config",
    "port": 3001,
    "arch": "amd64"
  }
}

响应:

{
  "data": {
    "session_token": "agt_sess_xxx",
    "session_expires_at": "2026-04-28T10:00:00Z",
    "heartbeat_interval_seconds": 15,
    "command_poll_interval_seconds": 5,
    "server_time": "2026-04-27T10:00:00Z"
  }
}

Agent 必须缓存 session_token,后续接口都使用它认证。

心跳

POST {base}/api/v1/agent/heartbeat
Authorization: Bearer {session_token}
{
  "agent_id": "myruntime-123-main",
  "timestamp": "2026-04-27T10:00:15Z",
  "openclaw_status": "running",
  "summary": {
    "runtime_type": "myruntime",
    "runtime_status": "running",
    "runtime_pid": 245,
    "runtime_version": "0.4.0",
    "openclaw_pid": 245,
    "skill_count": 8,
    "disk_used_bytes": 2147483648,
    "disk_limit_bytes": 10737418240
  }
}

兼容要求:

  • openclaw_status 当前仍是 v1 状态槽位。新 runtime 填自己的主进程状态。
  • summary.openclaw_pid 是当前后端识别 PID 的兼容别名;建议同时上报 runtime_pidopenclaw_pid,后续协议升级后再收敛。
  • 状态建议使用 startingrunningstoppederrorunknown
  • 心跳按服务端响应间隔执行,默认约 15 秒。
  • ClawManager 45 秒内收到心跳显示 online,45 到 120 秒显示 stale,超过 120 秒显示 offline。

完整状态和监测数据上报

POST {base}/api/v1/agent/state/report
Authorization: Bearer {session_token}
{
  "agent_id": "myruntime-123-main",
  "reported_at": "2026-04-27T10:00:20Z",
  "runtime": {
    "openclaw_status": "running",
    "openclaw_pid": 245,
    "openclaw_version": "myruntime-0.4.0",
    "current_config_revision_id": null
  },
  "system_info": {
    "runtime_type": "myruntime",
    "runtime_name": "My Runtime",
    "sampled_at": "2026-04-27T10:00:20Z",
    "cpu": {
      "cores": 2,
      "load": {
        "1m": 0.64,
        "5m": 0.52,
        "15m": 0.40
      }
    },
    "memory": {
      "mem_total_bytes": 4294967296,
      "mem_available_bytes": 2147483648
    },
    "disk": {
      "mount_path": "/config",
      "root_total_bytes": 10737418240,
      "root_free_bytes": 8589934592
    },
    "network": {
      "interfaces": [
        {
          "name": "eth0",
          "status": "up",
          "addresses": ["10.42.0.12"],
          "rx_bytes": 123456789,
          "tx_bytes": 98765432
        }
      ]
    }
  },
  "health": {
    "runtime_process": "ok",
    "desktop": "ok",
    "agent": "ok",
    "metrics_collector": "ok",
    "metrics_sample_interval_seconds": 5
  }
}

后端会原样保存 system_infohealth。前端实例详情页当前按以下字段绘制指标:

路径 类型 单位 说明
system_info.cpu.cores number 核数 容器可用 CPU 核数
system_info.cpu.load.1m number load average 1 分钟 load
system_info.cpu.load.5m number load average 5 分钟 load
system_info.cpu.load.15m number load average 15 分钟 load
system_info.memory.mem_total_bytes number bytes 内存上限或总内存
system_info.memory.mem_available_bytes number bytes 可用内存
system_info.disk.root_total_bytes number bytes 持久化目录所在文件系统总容量
system_info.disk.root_free_bytes number bytes 持久化目录所在文件系统剩余容量
system_info.network.interfaces[].rx_bytes number bytes 网卡累计接收字节数
system_info.network.interfaces[].tx_bytes number bytes 网卡累计发送字节数

CPU 百分比由前端按 load.1m / cores * 100 计算。内存百分比由 (mem_total_bytes - mem_available_bytes) / mem_total_bytes * 100 计算。磁盘百分比由 (root_total_bytes - root_free_bytes) / root_total_bytes * 100 计算。网络速率由相邻两次 rx_bytestx_bytes counter 差值计算,所以 agent 必须上报单调递增的累计 counter,不要把瞬时速率填进这两个字段。

推荐采样来源:

  • CPU load:/proc/loadavg
  • CPU cores:优先 cgroup CPU quota,例如 cgroup v2 /sys/fs/cgroup/cpu.max;没有 quota 时使用 /proc/cpuinfo 或语言运行时 CPU 数。
  • 内存:优先 cgroup memory limit/current,例如 cgroup v2 /sys/fs/cgroup/memory.max/sys/fs/cgroup/memory.current;没有限制时使用 /proc/meminfoMemTotalMemAvailable
  • 磁盘:对 CLAWMANAGER_AGENT_PERSISTENT_DIR 调用 statvfs
  • 网络:读取 /proc/net/dev,默认排除 lo

上报频率:

  • 启动成功后立即发送一次。
  • 正常运行每 5 秒发送一次;资源敏感时可放宽到 10 秒。
  • 采样间隔不要短于 2 秒。
  • runtime 状态变化、skill inventory 变化、命令完成后立即补发一次。

命令轮询和执行

拉取命令:

GET {base}/api/v1/agent/commands/next
Authorization: Bearer {session_token}

如果没有命令,响应的 data 可能为空。拿到命令后,agent 必须先标记开始,再执行:

POST {base}/api/v1/agent/commands/{id}/start
Authorization: Bearer {session_token}
{
  "agent_id": "myruntime-123-main",
  "started_at": "2026-04-27T10:01:00Z"
}

完成后必须 finish,失败也要 finish:

POST {base}/api/v1/agent/commands/{id}/finish
Authorization: Bearer {session_token}
{
  "agent_id": "myruntime-123-main",
  "status": "succeeded",
  "finished_at": "2026-04-27T10:01:05Z",
  "result": {
    "message": "system info collected"
  },
  "error_message": ""
}

当前通用命令类型:

命令 Agent 行为
collect_system_info 立即采样,发送 state report,并在 finish result 中带上同一份摘要
health_check 检查主进程、桌面入口、agent、metrics collector,并发送 state report
sync_skill_inventory 扫描 skill 目录并上报完整 inventory
refresh_skill_inventory 重新扫描 skill 目录并上报完整 inventory
collect_skill_package 打包指定 skill 并上传
install_skill 下载并安装平台指定 skill version
update_skill 更新已安装 skill
uninstall_skill / remove_skill 移除指定 skill
disable_skill 禁用指定 skill
quarantine_skill 隔离指定 skill
handle_skill_risk 按平台风控 payload 处理 skill
apply_config_revision 获取并应用配置 revision
reload_config 重新加载 runtime 配置

start_openclawstop_openclawrestart_openclaw 是历史命名命令。非 OpenClaw runtime 不应误执行,除非平台侧明确把它们映射为该 runtime 的启动、停止、重启语义。

Agent 遇到未知命令时,应 finish 为 failederror_message 写明 unsupported command type: <type>

Skill inventory

上报:

POST {base}/api/v1/agent/skills/inventory
Authorization: Bearer {session_token}
{
  "agent_id": "myruntime-123-main",
  "reported_at": "2026-04-27T10:02:00Z",
  "mode": "full",
  "trigger": "startup",
  "skills": [
    {
      "skill_id": "filesystem-name-or-manifest-id",
      "skill_version": "1.0.0",
      "identifier": "vendor.skill-name",
      "install_path": "/config/myruntime/skills/vendor.skill-name",
      "content_md5": "0123456789abcdef0123456789abcdef",
      "source": "runtime",
      "type": "agent-skill",
      "size_bytes": 12345,
      "file_count": 12,
      "collected_at": "2026-04-27T10:02:00Z",
      "metadata": {
        "runtime_type": "myruntime"
      }
    }
  ]
}

要求:

  • identifiercontent_md5 必须稳定。
  • mode=full 表示这次 inventory 是全量结果,平台会用它对齐实例 skill 状态。
  • skill 内容变化后要重新计算 content_md5 并上报。
  • 如果支持上传 skill 包,使用 POST {base}/api/v1/agent/skills/upload,multipart 表单中带 fileagent_idskill_idskill_versionidentifiercontent_md5source
  • content_md5 必须按目录内容指纹计算,不是 zip 文件 MD5。完整算法见 Skill Content MD5 Calculation Spec

配置和安装包下载

应用配置 revision:

GET {base}/api/v1/agent/config/revisions/{id}
Authorization: Bearer {session_token}

下载 skill version:

GET {base}/api/v1/agent/skills/versions/{external_version_id}/download
Authorization: Bearer {session_token}

下载内容只能应用到当前实例,不应泄露到其他实例或日志。

错误处理和重试

Agent 应按下面规则处理错误:

情况 处理
HTTP 401 删除本地 session token,使用 bootstrap token 重新注册
HTTP 403 停止重试当前认证路径,记录简短错误,等待配置修复
HTTP 404 对命令或资源类请求标记失败,错误写入 finish
HTTP 429 / 5xx / 网络错误 指数退避重试,保留本地状态
JSON 校验失败 修正 payload;命令执行场景下 finish 为 failed
采样部分失败 上报可用字段,并在 health.metrics_collectordegradederror

推荐退避:1s、2s、5s、10s、30s,最大 60s,并加入少量 jitter。

安全要求

  • 不记录 token、API key、完整 Authorization header。
  • session token 只写入当前实例持久化目录,文件权限建议 0600
  • 不接受来自用户输入的任意 URL 覆盖 CLAWMANAGER_AGENT_BASE_URL
  • 上传 skill 包前需要限制目录边界,不能打包持久化目录以外的文件。
  • 解压平台下发的 skill 包时必须防止 zip slip,例如拒绝 ../ 和绝对路径。
  • 命令执行要做超时控制,超过 timeout_seconds 应主动终止并 finish 为 failed。

验收标准

新增 runtime agent 接入完成后,至少满足:

  1. 创建实例后容器环境中存在 Agent 和 AI Gateway 变量。
  2. Agent 能注册成功,实例详情页显示 agent online。
  3. 心跳持续发送,45 秒内不会变 stale。
  4. GET /api/v1/instances/{instance_id}/runtime 能看到 runtime.system_inforuntime.health
  5. CPU、Memory、Disk、Network 指标在实例详情页 10 秒内开始刷新。
  6. collect_system_info 命令能成功完成,并更新 state report。
  7. health_check 命令能成功完成,并体现 runtime、agent、metrics collector 状态。
  8. Skill inventory 能上报全量结果,skill 变化后能重新同步。
  9. session token 过期或 401 后能自动重新注册。
  10. 日志中没有 bootstrap token、session token、AI Gateway API key。

最小实现伪代码

load env
if CLAWMANAGER_AGENT_ENABLED != "true":
  sleep forever

agent_id = "<runtime_type>-" + CLAWMANAGER_AGENT_INSTANCE_ID + "-main"
session = load session from persistent dir
if session missing:
  session = register_with_bootstrap_token()
  save session

report_state(sample_system_info(), health_check())
sync_skill_inventory(trigger="startup")

loop:
  every heartbeat_interval:
    resp = heartbeat(summary)
    if resp.has_pending_command:
      poll_and_execute_commands()

  every command_poll_interval:
    poll_and_execute_commands()

  every 5 seconds:
    report_state(sample_system_info(), health_snapshot)

  on 401:
    delete session
    session = register_with_bootstrap_token()
    save session