Files
yuan-lab-llm--clawmanager/docs/runtime-agent-integration-guide.md
wehub-resource-sync 6d978fe483
Build / Frontend Build (push) Waiting to run
Build / Backend Build (push) Waiting to run
Build / Docker Build (push) Blocked by required conditions
Build / Kubernetes Deploy Smoke Test (push) Blocked by required conditions
chore: import upstream snapshot with attribution
2026-07-13 12:35:24 +08:00

18 KiB
Raw Permalink Blame History

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 秒内收到心跳显示 online45 到 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/uploadmultipart 表单中带 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