58c19856fd
* feat(runtime): improve Lite gateway scheduling and proxy stability - Expand the runtime gateway port range to match 100 instances per pod - Scale runtime deployments based on pending backlog - Serialize gateway creation per pod and handle starting/creating states - Clean up missing gateway bindings from runtime agent reports - Improve Lite gateway proxy token stripping and auth header injection - Update deployment manifests, docs, and related tests * fix(runtime): harden Hermes Lite gateway recovery - Add hashed gateway token aliases for short-term token compatibility - Fix /resume conversations failing when old sessions use mismatched gateway tokens - Recover ports only from stale error bindings without affecting active gateways * feat(runtime): enable bidirectional desktop clipboard and direct proxy - Enable WebTop/Selkies/Kasm clipboard by default with per-instance overrides - Document clipboard verification, configuration pitfalls, and IME troubleshooting - Enable direct desktop proxy in the K8s and K3s manifests --------- Co-authored-by: litiantian03 <litiantian03@ieisystem.com>
889 lines
26 KiB
Markdown
889 lines
26 KiB
Markdown
# ClawManager Agent V2 开发规范
|
||
|
||
本文整理当前 ClawManager 代码中的最新 agent 约定,面向 OpenClaw、Hermes 以及后续新增的托管 runtime。开发或改造 runtime 镜像时,应优先遵守本文;字段级契约以 `docs/clawmanager-agent-v2-contract.md` 和后端代码为准。
|
||
|
||
## 1. 架构定位
|
||
|
||
最新架构中有两类 agent 通信面:
|
||
|
||
1. **Runtime Pod Agent,也就是本文主线的 V2 agent**
|
||
运行在共享 runtime Pod 内,负责本 Pod 内多个 gateway 子进程的创建、删除、端口、workspace、资源隔离、健康检查和状态上报。ClawManager 通过它把一个实例绑定到 `pod_ip + gateway_port`。
|
||
|
||
2. **Instance Agent Control Plane,旧的 `/api/v1/agent/*` 协议**
|
||
面向单个实例的状态、命令、skill inventory、skill package 上传和配置 revision 下载。当前后端仍保留这套协议,runtime 可以继续复用它完成实例级状态与 skill 管理;但 gateway 生命周期管理必须走 Runtime Pod Agent。
|
||
|
||
一句话原则:**Runtime Pod Agent 是 Pod 内 gateway 管理器,不是一个阻塞式启动脚本。**
|
||
|
||
## 2. 支持范围
|
||
|
||
当前 V2 托管 runtime 类型:
|
||
|
||
| 类型 | 说明 |
|
||
| --- | --- |
|
||
| `openclaw` | OpenClaw runtime |
|
||
| `hermes` | Hermes runtime |
|
||
|
||
当前平台默认值:
|
||
|
||
| 项 | 默认值 |
|
||
| --- | --- |
|
||
| workspace root | `/workspaces` |
|
||
| workspace path | `/workspaces/{runtime}/user-{user_id}/instance-{instance_id}` |
|
||
| gateway 端口范围 | `20000-20299` |
|
||
| 单 Runtime Pod 容量 | `100` |
|
||
| 实例 UID/GID | `200000 + instance_id` |
|
||
| agent 控制端口 | `19090` |
|
||
|
||
## 3. Runtime Pod Agent 职责
|
||
|
||
Runtime Pod Agent 必须实现:
|
||
|
||
- 启动本地控制服务:`GET /v1/health`、`POST /v1/gateways`、`DELETE /v1/gateways/{gateway_id}`、`POST /v1/drain`。
|
||
- 向 ClawManager 上报 runtime pod 注册、heartbeat、metrics、gateway 状态和 skill 状态。
|
||
- 管理本 Pod 内 gateway 子进程生命周期。
|
||
- 管理端口池,避免同一 Pod 内端口冲突。
|
||
- 创建并校验 workspace,设置正确 UID/GID。
|
||
- 将 ClawManager 下发的 LLM、代理、实例 token、runtime 配置写入实例自己的 workspace。
|
||
- 实现 drain,拒绝新 gateway,保持已有 gateway 运行,等待 ClawManager 迁移和删除。
|
||
- 避免任何敏感 token、API key、Authorization header 出现在日志中。
|
||
|
||
不应该由 Runtime Pod Agent 实现:
|
||
|
||
- 直接对浏览器暴露 gateway 自签地址。
|
||
- 管理 ClawManager 用户权限。
|
||
- 删除用户 workspace。
|
||
- 把外部 NodePort、外部 HTTPS 入口或固定内网 IP 写死进 runtime 配置。
|
||
|
||
## 4. 运行环境变量
|
||
|
||
当前 Runtime Deployment 构建器注入以下变量,agent 实现应优先支持:
|
||
|
||
| 变量 | 说明 |
|
||
| --- | --- |
|
||
| `CLAWMANAGER_RUNTIME_TYPE` | `openclaw` 或 `hermes` |
|
||
| `CLAWMANAGER_AGENT_PORT` | agent 控制端口,当前为 `19090` |
|
||
| `CLAWMANAGER_GATEWAY_PORT_START` | gateway 起始端口 |
|
||
| `CLAWMANAGER_GATEWAY_PORT_END` | gateway 结束端口 |
|
||
| `CLAWMANAGER_AGENT_CONTROL_TOKEN` | ClawManager 调 agent 控制接口使用 |
|
||
| `CLAWMANAGER_AGENT_REPORT_TOKEN` | agent 上报 ClawManager 使用 |
|
||
|
||
为了兼容早期文档和旧镜像,agent 建议同时识别以下别名:
|
||
|
||
| 新变量 | 兼容别名 |
|
||
| --- | --- |
|
||
| `CLAWMANAGER_AGENT_CONTROL_TOKEN` | `RUNTIME_AGENT_CONTROL_TOKEN` |
|
||
| `CLAWMANAGER_AGENT_REPORT_TOKEN` | `RUNTIME_AGENT_REPORT_TOKEN` |
|
||
| `CLAWMANAGER_GATEWAY_PORT_START` | `RUNTIME_GATEWAY_PORT_START` |
|
||
| `CLAWMANAGER_GATEWAY_PORT_END` | `RUNTIME_GATEWAY_PORT_END` |
|
||
| `CLAWMANAGER_AGENT_PORT` | `RUNTIME_AGENT_PUBLIC_PORT` |
|
||
|
||
建议支持的附加变量:
|
||
|
||
| 变量 | 默认值/要求 |
|
||
| --- | --- |
|
||
| `RUNTIME_WORKSPACE_ROOT` | 默认 `/workspaces` |
|
||
| `RUNTIME_AGENT_LISTEN_ADDR` | 默认 `0.0.0.0:19090` |
|
||
| `CLAWMANAGER_BACKEND_URL` | ClawManager backend 内部地址,推荐 Kubernetes Service DNS |
|
||
| `CLAWMANAGER_RUNTIME_IMAGE_REF` | 当前镜像标识,用于注册上报 |
|
||
| `POD_NAME` / `POD_NAMESPACE` / `POD_IP` / `NODE_NAME` | 建议通过 Downward API 注入 |
|
||
| `CLAWMANAGER_TRUSTED_PROXY_CIDRS` | ClawManager app/gateway 的可信代理网段 |
|
||
|
||
`CLAWMANAGER_BACKEND_URL` 必须使用集群内部地址,例如:
|
||
|
||
```text
|
||
http://clawmanager-gateway.clawmanager-system.svc.cluster.local:9001
|
||
```
|
||
|
||
不要在 runtime 内部配置中写入浏览器外部入口,例如 `https://172.16.1.12:39443`。
|
||
|
||
## 5. Agent 到 ClawManager 的上报接口
|
||
|
||
所有 Runtime Pod Agent 上报请求必须带:
|
||
|
||
```http
|
||
X-ClawManager-Agent-Token: ${CLAWMANAGER_AGENT_REPORT_TOKEN}
|
||
Content-Type: application/json
|
||
```
|
||
|
||
后端统一返回:
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"message": "...",
|
||
"data": {}
|
||
}
|
||
```
|
||
|
||
### 5.1 注册 Runtime Pod
|
||
|
||
```http
|
||
POST {CLAWMANAGER_BACKEND_URL}/api/v1/runtime-agent/register
|
||
```
|
||
|
||
必填字段:
|
||
|
||
- `runtime_type`
|
||
- `namespace`
|
||
- `pod_name`
|
||
- `deployment_name`
|
||
- `image_ref`
|
||
|
||
推荐完整 payload:
|
||
|
||
```json
|
||
{
|
||
"runtime_type": "openclaw",
|
||
"namespace": "clawmanager-system",
|
||
"pod_name": "openclaw-runtime-6f77f8b8c7-abcde",
|
||
"pod_uid": "pod-uid",
|
||
"pod_ip": "10.42.0.31",
|
||
"node_name": "node-a",
|
||
"deployment_name": "openclaw-runtime",
|
||
"image_ref": "ghcr.io/yuan-lab-llm/agentsruntime/openclaw:latest",
|
||
"agent_endpoint": "http://10.42.0.31:19090",
|
||
"state": "ready",
|
||
"capacity": 100,
|
||
"used_slots": 0,
|
||
"draining": false,
|
||
"metrics": {
|
||
"agent_version": "0.1.0"
|
||
},
|
||
"reported_at": "2026-06-08T09:00:00Z"
|
||
}
|
||
```
|
||
|
||
要求:
|
||
|
||
- `agent_endpoint` 必须是 ClawManager Pod 可以访问的地址,通常是 `http://{POD_IP}:19090`,不能是 `127.0.0.1`。
|
||
- `capacity <= 0` 时后端会按 `100` 处理,但 agent 应主动上报真实容量。
|
||
- `state` 为空时后端按 `ready` 处理;agent 不应在初始化未完成时上报 ready。
|
||
- `metrics` 必须是合法 JSON。
|
||
|
||
### 5.2 Heartbeat
|
||
|
||
```http
|
||
POST {CLAWMANAGER_BACKEND_URL}/api/v1/runtime-agent/heartbeat
|
||
```
|
||
|
||
payload:
|
||
|
||
```json
|
||
{
|
||
"pod_id": 17,
|
||
"namespace": "clawmanager-system",
|
||
"pod_name": "openclaw-runtime-6f77f8b8c7-abcde",
|
||
"state": "ready",
|
||
"used_slots": 37,
|
||
"draining": false,
|
||
"reported_at": "2026-06-08T09:00:02Z"
|
||
}
|
||
```
|
||
|
||
要求:
|
||
|
||
- 优先使用注册返回的 `data.pod.id` 作为 `pod_id`。
|
||
- 没有 `pod_id` 时必须带 `namespace + pod_name`。
|
||
- `state` 必填。
|
||
- `used_slots` 必须来自当前真实 `starting/running` gateway 数量,不要使用过期缓存。
|
||
- 建议 heartbeat 间隔 2 秒;如果环境压力较大,可放宽,但必须小于 ClawManager 的 heartbeat 超时窗口。
|
||
|
||
### 5.3 Metrics
|
||
|
||
```http
|
||
POST {CLAWMANAGER_BACKEND_URL}/api/v1/runtime-agent/metrics/report
|
||
```
|
||
|
||
payload:
|
||
|
||
```json
|
||
{
|
||
"pod_id": 17,
|
||
"cpu_millis_used": 13600,
|
||
"memory_bytes_used": 42949672960,
|
||
"disk_bytes_used": 214748364800,
|
||
"network_rx_bytes": 9223372,
|
||
"network_tx_bytes": 19223372,
|
||
"metrics": {
|
||
"gateway_count": 37,
|
||
"load_1m": 2.4
|
||
},
|
||
"reported_at": "2026-06-08T09:00:05Z"
|
||
}
|
||
```
|
||
|
||
要求:
|
||
|
||
- CPU 单位是 millicore。
|
||
- memory/disk/network 单位是 bytes。
|
||
- network 字段必须是单调递增 counter,不要填瞬时速率。
|
||
- `metrics` 是扩展 JSON,必须合法。
|
||
|
||
### 5.4 Gateway Report
|
||
|
||
```http
|
||
POST {CLAWMANAGER_BACKEND_URL}/api/v1/runtime-agent/gateways/report
|
||
```
|
||
|
||
payload:
|
||
|
||
```json
|
||
{
|
||
"pod_id": 17,
|
||
"gateways": [
|
||
{
|
||
"instance_id": 123,
|
||
"gateway_id": "gw-123-7",
|
||
"gateway_port": 20017,
|
||
"gateway_pid": 8842,
|
||
"state": "running",
|
||
"generation": 7,
|
||
"health_at": "2026-06-08T09:00:05Z"
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
要求:
|
||
|
||
- `gateways` 必填。
|
||
- 每个 gateway 的 `instance_id`、`state`、`generation` 必填。
|
||
- 后端只接受“当前绑定 Pod + 当前 generation”的上报;旧 Pod 或旧 generation 上报会被忽略。
|
||
- `state=running` 或 `state=healthy` 会把绑定更新为 running。
|
||
- `starting`、`stopped`、`error`、`unhealthy` 等状态会更新绑定状态。
|
||
- 错误状态必须带 `error_message`,不要只上报空状态。
|
||
|
||
### 5.5 Skills Report
|
||
|
||
```http
|
||
POST {CLAWMANAGER_BACKEND_URL}/api/v1/runtime-agent/skills/report
|
||
```
|
||
|
||
当前后端接收并发布任意 JSON payload。推荐按实例分组:
|
||
|
||
```json
|
||
{
|
||
"pod_id": 17,
|
||
"runtime_type": "openclaw",
|
||
"mode": "full",
|
||
"reported_at": "2026-06-08T09:00:05Z",
|
||
"instances": [
|
||
{
|
||
"instance_id": 123,
|
||
"workspace_path": "/workspaces/openclaw/user-45/instance-123",
|
||
"skills": [
|
||
{
|
||
"skill_id": "weather",
|
||
"skill_version": "1.0.0",
|
||
"identifier": "weather",
|
||
"install_path": "/workspaces/openclaw/user-45/instance-123/.openclaw/skills/weather",
|
||
"content_md5": "0123456789abcdef0123456789abcdef",
|
||
"source": "runtime",
|
||
"type": "agent-skill"
|
||
}
|
||
]
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
skill inventory 必须按 `instance_id` 和 `workspace_path` 隔离,不要把一个实例的 skill 混到另一个实例。
|
||
|
||
## 6. ClawManager 到 Agent 的控制接口
|
||
|
||
所有控制请求必须校验:
|
||
|
||
```http
|
||
X-ClawManager-Control-Token: ${CLAWMANAGER_AGENT_CONTROL_TOKEN}
|
||
```
|
||
|
||
token 错误返回 `401`。不要返回 `3xx` redirect;后端会把 redirect 当成失败。
|
||
|
||
### 6.1 Health
|
||
|
||
```http
|
||
GET /v1/health
|
||
```
|
||
|
||
任意 HTTP 2xx 都会被后端视为成功。建议响应:
|
||
|
||
```json
|
||
{
|
||
"status": "ready"
|
||
}
|
||
```
|
||
|
||
以下情况必须返回 `503`:
|
||
|
||
- 必填环境变量缺失。
|
||
- workspace root 不可访问。
|
||
- 端口池不可用。
|
||
- control/report token 缺失。
|
||
- clock skew 超过健康阈值。
|
||
- agent 正在关闭,不再接受新请求。
|
||
|
||
### 6.2 Create Gateway
|
||
|
||
```http
|
||
POST /v1/gateways
|
||
```
|
||
|
||
请求:
|
||
|
||
```json
|
||
{
|
||
"instance_id": 123,
|
||
"user_id": 45,
|
||
"agent_type": "openclaw",
|
||
"workspace_path": "/workspaces/openclaw/user-45/instance-123",
|
||
"port_range": {
|
||
"start": 20000,
|
||
"end": 20299
|
||
},
|
||
"uid": 200123,
|
||
"gid": 200123,
|
||
"cpu_cores": 2,
|
||
"memory_mb": 4096,
|
||
"disk_quota_mb": 20480,
|
||
"generation": 7,
|
||
"environment": {
|
||
"CLAWMANAGER_LLM_BASE_URL": "http://clawmanager-gateway.clawmanager-system.svc.cluster.local:9001/api/v1/gateway/llm",
|
||
"CLAWMANAGER_LLM_API_KEY": "instance-token",
|
||
"CLAWMANAGER_LLM_MODEL": "[\"auto\"]",
|
||
"CLAWMANAGER_LLM_PROVIDER": "openai-compatible",
|
||
"CLAWMANAGER_INSTANCE_TOKEN": "instance-token",
|
||
"OPENAI_API_KEY": "instance-token"
|
||
}
|
||
}
|
||
```
|
||
|
||
响应:
|
||
|
||
```json
|
||
{
|
||
"gateway_id": "gw-123-7",
|
||
"port": 20017,
|
||
"pid": 8842,
|
||
"status": "starting"
|
||
}
|
||
```
|
||
|
||
实现要求:
|
||
|
||
- handler 必须快速返回,通常 1 到 3 秒内返回 `starting` 或已有 gateway 元数据。
|
||
- 不能在 HTTP handler 内阻塞等待前台 gateway 进程完整运行。
|
||
- `agent_type` 必须等于当前 Pod runtime type。
|
||
- Pod draining 时返回 `409` 或 `503`,拒绝新建。
|
||
- 以 `instance_id + generation` 为幂等键;重复请求不能重复启动进程。
|
||
- 收到更高 generation 时,停止同实例旧 generation gateway 并释放端口。
|
||
- 端口必须来自请求的 `port_range`。
|
||
- 没有可用端口时返回:
|
||
|
||
```http
|
||
HTTP/1.1 409 Conflict
|
||
Content-Type: text/plain
|
||
|
||
```
|
||
|
||
- 启动失败、健康检查失败、配置写入失败时,上报 gateway `error` 并释放端口。
|
||
|
||
### 6.3 Delete Gateway
|
||
|
||
```http
|
||
DELETE /v1/gateways/{gateway_id}
|
||
```
|
||
|
||
要求:
|
||
|
||
- `gateway_id` 是 URL path escaped,agent 必须 decode 后再查找。
|
||
- 优雅停止进程组,超时后强制 kill。
|
||
- 释放端口、cgroup、进程表和本地状态。
|
||
- 不删除 workspace。
|
||
- gateway 不存在时建议返回 `204`,保持删除幂等。
|
||
- 成功状态可以是 `200`、`202` 或 `204`。
|
||
|
||
### 6.4 Drain
|
||
|
||
```http
|
||
POST /v1/drain
|
||
```
|
||
|
||
请求:
|
||
|
||
```json
|
||
{
|
||
"draining": true
|
||
}
|
||
```
|
||
|
||
要求:
|
||
|
||
- 设置本地 draining 状态。
|
||
- 立即拒绝新的 `POST /v1/gateways`。
|
||
- 已有 gateway 不主动停止,继续健康检查和上报。
|
||
- 立即补发 heartbeat,`state=draining, draining=true`。
|
||
- 等 ClawManager 迁移实例后,逐个收到 DELETE 再清理。
|
||
|
||
## 7. Gateway 生命周期
|
||
|
||
推荐状态机:
|
||
|
||
```text
|
||
requested -> reserved_port -> starting -> running
|
||
-> error
|
||
running -> stopping -> stopped
|
||
running -> unhealthy -> running/error
|
||
```
|
||
|
||
创建流程:
|
||
|
||
1. 校验 token、runtime type、instance/user/generation、workspace、端口范围。
|
||
2. 如果 draining,拒绝创建。
|
||
3. 查本地状态,处理幂等。
|
||
4. 如果 generation 更高,清理旧 generation。
|
||
5. 加锁分配端口,标记为 reserved。
|
||
6. 创建 workspace 和 home,设置 owner 为请求中的 `uid/gid`。
|
||
7. 写入 runtime 配置和 LLM/proxy 配置。
|
||
8. 持久化 gateway 元数据为 `starting`。
|
||
9. 后台启动 gateway 子进程,立即返回。
|
||
10. 健康检查通过后上报 `running`。
|
||
11. 失败时上报 `error`,记录短错误信息并释放端口。
|
||
|
||
`running` 必须以真实进程和真实健康检查为准,不能只读旧状态文件。
|
||
|
||
## 8. 端口管理
|
||
|
||
agent 必须实现并发安全的端口分配器:
|
||
|
||
```text
|
||
reserve(instance_id, generation, count) -> port_set
|
||
commit(instance_id, generation, port_set)
|
||
release(instance_id, generation)
|
||
list_used()
|
||
```
|
||
|
||
要求:
|
||
|
||
- 分配时持有互斥锁。
|
||
- 在锁内检查 agent 内存状态和系统监听端口。
|
||
- 启动失败、健康检查失败、DELETE 成功后必须释放端口。
|
||
- 如果 runtime 需要多个端口,必须一次性分配端口组,避免部分成功。
|
||
- 同一 Pod 内端口不能冲突;不同 Pod 可以复用相同端口,因为路由使用 `pod_ip + port`。
|
||
|
||
## 9. Workspace 与资源隔离
|
||
|
||
workspace 固定格式:
|
||
|
||
```text
|
||
/workspaces/{runtime}/user-{user_id}/instance-{instance_id}
|
||
```
|
||
|
||
agent 必须:
|
||
|
||
- 对 workspace root 和请求路径执行 realpath。
|
||
- 拒绝 `..`、绝对路径逃逸、符号链接逃逸、跨用户、跨实例路径。
|
||
- 创建 `${workspace}/home`,并设置 `HOME=${workspace}/home`。
|
||
- owner 设置为请求中的 `uid/gid`,当前默认 `200000 + instance_id`。
|
||
- token、provider、配置文件权限建议 `0600`。
|
||
- 删除 gateway 时不删除 workspace。
|
||
|
||
资源限制要求:
|
||
|
||
- 每个 gateway 使用独立进程组。
|
||
- 业务进程最终以实例 UID/GID 运行。
|
||
- CPU/Memory 使用 cgroup 限制。
|
||
- Disk 优先使用 filesystem quota;不支持时至少周期扫描并在超限时上报错误。
|
||
- 资源限制无法启用时必须在 report 或错误消息中体现,不要静默忽略。
|
||
|
||
## 10. Runtime 配置和 LLM 注入
|
||
|
||
ClawManager 会通过 `POST /v1/gateways` 的 `environment` 字段下发实例级 LLM 环境变量。agent 只能转发白名单变量:
|
||
|
||
```text
|
||
CLAWMANAGER_LLM_BASE_URL
|
||
CLAWMANAGER_LLM_API_KEY
|
||
CLAWMANAGER_LLM_MODEL
|
||
CLAWMANAGER_LLM_PROVIDER
|
||
CLAWMANAGER_INSTANCE_TOKEN
|
||
OPENAI_BASE_URL
|
||
OPENAI_API_BASE
|
||
OPENAI_API_KEY
|
||
OPENAI_MODEL
|
||
```
|
||
|
||
要求:
|
||
|
||
- LLM base URL 使用 ClawManager 内部 Service DNS。
|
||
- API key 使用当前实例 token。
|
||
- 不要求用户在 runtime 页面手工填写 OpenAI key。
|
||
- 不打印 token/API key。
|
||
- 如果缺少 LLM key,不能上报 gateway `running`;应上报 `error` 并说明缺少实例 LLM token。
|
||
|
||
runtime 配置必须写在实例 workspace 内,例如:
|
||
|
||
| Runtime | 推荐路径 |
|
||
| --- | --- |
|
||
| OpenClaw | `{workspace}/home/.openclaw/openclaw.json` |
|
||
| Hermes | `{workspace}/home/.hermes/hermes.json` 或 Hermes 原生配置路径 |
|
||
|
||
JSON merge 规则:
|
||
|
||
- 对象字段递归 merge。
|
||
- 保留用户已有未知字段。
|
||
- 平台必须控制的字段可以覆盖,但要在代码中显式列出。
|
||
- 数组 append 后去重,例如 `allowedOrigins`。
|
||
- 写入前先生成临时文件,再原子 rename。
|
||
|
||
## 11. 代理和浏览器访问
|
||
|
||
浏览器访问链路必须是:
|
||
|
||
```text
|
||
Browser HTTPS -> ClawManager HTTPS -> ClawManager backend proxy -> Runtime Pod HTTP gateway
|
||
```
|
||
|
||
agent 写 runtime 配置时必须保证:
|
||
|
||
- gateway 支持 `/api/v1/instances/{instance_id}/proxy` base path。
|
||
- allowed origin 使用 ClawManager 内部 origin,例如 `http://clawmanager-gateway.clawmanager-system.svc.cluster.local:9001`。
|
||
- trusted proxies 来自 `CLAWMANAGER_TRUSTED_PROXY_CIDRS` 或等价 env。
|
||
- 不使用外部地址作为 runtime 内部 trusted origin。
|
||
- 通过 ClawManager proxy 访问时,不应要求用户手工粘贴 gateway token。
|
||
|
||
## 12. 时间同步
|
||
|
||
agent 必须处理 clock skew:
|
||
|
||
- 注册和 heartbeat 响应如果带 `server_time`,记录本地时间和服务端时间偏移。
|
||
- 偏移超过 30 秒时记录 warning,并在 health 中暴露 `clock_skew_warning`。
|
||
- 偏移超过 120 秒时不应上报 `ready` 或 gateway `running`。
|
||
- 所有签名、token 时间和上报时间使用 UTC。
|
||
- 不在容器里修改系统时间,时间同步由 Kubernetes 节点 NTP/chrony 负责。
|
||
|
||
常见症状包括 `device signature expired`、JWT/WebSocket 鉴权异常、签名 URL 过期和 gateway 实际状态与 ClawManager 展示不一致。
|
||
|
||
## 13. Instance Agent v1 兼容协议
|
||
|
||
当前后端仍提供 `/api/v1/agent/*`,用于实例级状态、命令和 skill 管理。托管 runtime 如果需要复用这套能力,应由具体实例的 gateway/agent 逻辑使用实例级环境变量。
|
||
|
||
ClawManager 会为支持托管集成的实例注入:
|
||
|
||
| 变量 | 说明 |
|
||
| --- | --- |
|
||
| `CLAWMANAGER_AGENT_ENABLED` | `true` 时启用实例 agent |
|
||
| `CLAWMANAGER_AGENT_BASE_URL` | ClawManager API base URL,不带 `/api/v1/agent` 后缀 |
|
||
| `CLAWMANAGER_AGENT_BOOTSTRAP_TOKEN` | 首次注册使用 |
|
||
| `CLAWMANAGER_AGENT_INSTANCE_ID` | 当前实例 ID |
|
||
| `CLAWMANAGER_AGENT_PROTOCOL_VERSION` | 当前为 `v1` |
|
||
| `CLAWMANAGER_AGENT_PERSISTENT_DIR` | 当前实例持久化目录 |
|
||
| `CLAWMANAGER_AGENT_DISK_LIMIT_BYTES` | 实例磁盘配额 |
|
||
|
||
### 13.1 Register
|
||
|
||
```http
|
||
POST {base}/api/v1/agent/register
|
||
Authorization: Bearer ${CLAWMANAGER_AGENT_BOOTSTRAP_TOKEN}
|
||
```
|
||
|
||
payload:
|
||
|
||
```json
|
||
{
|
||
"instance_id": 123,
|
||
"agent_id": "openclaw-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": "openclaw",
|
||
"runtime_name": "OpenClaw",
|
||
"image": "ghcr.io/yuan-lab-llm/agentsruntime/openclaw:latest",
|
||
"persistent_dir": "/config"
|
||
}
|
||
}
|
||
```
|
||
|
||
响应 `data` 包含:
|
||
|
||
- `session_token`
|
||
- `session_expires_at`
|
||
- `heartbeat_interval_seconds`,当前默认 `15`
|
||
- `command_poll_interval_seconds`,当前默认 `5`
|
||
- `server_time`
|
||
|
||
session token 每次成功 heartbeat 会续期 24 小时。收到 `401` 时删除本地 session,并用 bootstrap token 重新注册。
|
||
|
||
### 13.2 Heartbeat
|
||
|
||
```http
|
||
POST {base}/api/v1/agent/heartbeat
|
||
Authorization: Bearer ${session_token}
|
||
```
|
||
|
||
payload:
|
||
|
||
```json
|
||
{
|
||
"agent_id": "openclaw-123-main",
|
||
"timestamp": "2026-06-08T09:00:15Z",
|
||
"openclaw_status": "running",
|
||
"current_config_revision_id": null,
|
||
"summary": {
|
||
"runtime_type": "openclaw",
|
||
"runtime_status": "running",
|
||
"runtime_pid": 245,
|
||
"openclaw_pid": 245,
|
||
"skill_count": 8,
|
||
"disk_used_bytes": 2147483648,
|
||
"disk_limit_bytes": 10737418240
|
||
}
|
||
}
|
||
```
|
||
|
||
兼容要求:
|
||
|
||
- `openclaw_status`、`openclaw_pid`、`openclaw_version` 是历史字段名;非 OpenClaw runtime 也需要填自己的主进程状态、PID 和版本,直到协议升级。
|
||
- 后端 45 秒内收到 heartbeat 展示 online,45 到 120 秒展示 stale,超过 120 秒展示 offline。
|
||
- heartbeat 响应里的 `has_pending_command=true` 时,应立即拉取命令。
|
||
|
||
### 13.3 State Report
|
||
|
||
```http
|
||
POST {base}/api/v1/agent/state/report
|
||
Authorization: Bearer ${session_token}
|
||
```
|
||
|
||
payload:
|
||
|
||
```json
|
||
{
|
||
"agent_id": "openclaw-123-main",
|
||
"reported_at": "2026-06-08T09:00:20Z",
|
||
"runtime": {
|
||
"openclaw_status": "running",
|
||
"openclaw_pid": 245,
|
||
"openclaw_version": "openclaw-2026.5.4",
|
||
"current_config_revision_id": null
|
||
},
|
||
"system_info": {
|
||
"runtime_type": "openclaw",
|
||
"cpu": {
|
||
"cores": 2,
|
||
"load": {
|
||
"1m": 0.64,
|
||
"5m": 0.52,
|
||
"15m": 0.4
|
||
}
|
||
},
|
||
"memory": {
|
||
"mem_total_bytes": 4294967296,
|
||
"mem_available_bytes": 2147483648
|
||
},
|
||
"disk": {
|
||
"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",
|
||
"agent": "ok",
|
||
"metrics_collector": "ok"
|
||
}
|
||
}
|
||
```
|
||
|
||
network 的 `rx_bytes` / `tx_bytes` 必须是单调递增 counter。
|
||
|
||
### 13.4 Commands
|
||
|
||
拉取:
|
||
|
||
```http
|
||
GET {base}/api/v1/agent/commands/next
|
||
Authorization: Bearer ${session_token}
|
||
```
|
||
|
||
开始:
|
||
|
||
```http
|
||
POST {base}/api/v1/agent/commands/{id}/start
|
||
Authorization: Bearer ${session_token}
|
||
```
|
||
|
||
完成:
|
||
|
||
```http
|
||
POST {base}/api/v1/agent/commands/{id}/finish
|
||
Authorization: Bearer ${session_token}
|
||
```
|
||
|
||
finish 只允许:
|
||
|
||
- `succeeded`
|
||
- `failed`
|
||
|
||
当前支持的命令类型:
|
||
|
||
```text
|
||
start_openclaw
|
||
stop_openclaw
|
||
restart_openclaw
|
||
collect_system_info
|
||
apply_config_revision
|
||
reload_config
|
||
health_check
|
||
install_skill
|
||
update_skill
|
||
uninstall_skill
|
||
remove_skill
|
||
disable_skill
|
||
quarantine_skill
|
||
handle_skill_risk
|
||
sync_skill_inventory
|
||
refresh_skill_inventory
|
||
collect_skill_package
|
||
```
|
||
|
||
未知命令必须 finish 为 `failed`,`error_message` 写明 `unsupported command type: <type>`。
|
||
|
||
### 13.5 Skills
|
||
|
||
inventory:
|
||
|
||
```http
|
||
POST {base}/api/v1/agent/skills/inventory
|
||
Authorization: Bearer ${session_token}
|
||
```
|
||
|
||
upload:
|
||
|
||
```http
|
||
POST {base}/api/v1/agent/skills/upload
|
||
Authorization: Bearer ${session_token}
|
||
Content-Type: multipart/form-data
|
||
```
|
||
|
||
上传表单字段:
|
||
|
||
- `file`
|
||
- `agent_id`
|
||
- `skill_id`
|
||
- `skill_version`
|
||
- `identifier`
|
||
- `content_md5`
|
||
- `source`
|
||
|
||
要求:
|
||
|
||
- `identifier` 和 `content_md5` 必须稳定。
|
||
- `mode=full` 的 inventory 代表全量结果,平台会用它对齐实例 skill 状态。
|
||
- `content_md5` 按目录内容指纹计算,不是 zip 文件 MD5;算法见 `docs/skill-content-md5-spec.md`。
|
||
- 打包和解压必须防止路径逃逸。
|
||
|
||
## 14. 安全规范
|
||
|
||
- 控制接口只接受正确 `X-ClawManager-Control-Token`。
|
||
- 上报接口只使用 `X-ClawManager-Agent-Token`。
|
||
- 实例级接口使用 Bearer bootstrap/session token。
|
||
- 日志不得打印 token、API key、完整 Authorization header、channel secret。
|
||
- 启动进程使用 argv 数组,不使用 shell 拼接:
|
||
|
||
```text
|
||
execve(binary, ["openclaw", "gateway", "run", "--port", "20017"], env)
|
||
```
|
||
|
||
- 不允许用户环境变量覆盖 agent 自己的 control/report token。
|
||
- 不允许 `allowedOrigins=["*"]` 作为生产配置。
|
||
- 所有用户输入路径都要边界校验。
|
||
- 命令执行必须有超时,超过 `timeout_seconds` 主动终止并 finish failed。
|
||
|
||
## 15. 测试要求
|
||
|
||
单元测试至少覆盖:
|
||
|
||
- Runtime type 只接受 `openclaw` / `hermes`。
|
||
- create gateway 幂等。
|
||
- 更高 generation 替换旧 generation。
|
||
- 端口并发分配无冲突。
|
||
- 端口被系统占用时跳过。
|
||
- 端口耗尽返回 `409 no free port`。
|
||
- workspace path 越权被拒绝。
|
||
- JSON merge 保留用户字段,数组去重。
|
||
- LLM env 白名单转发。
|
||
- 缺少 LLM token 时 gateway 不报 `running`。
|
||
- trusted proxy 和 allowed origin 配置生成。
|
||
- clock skew 阈值判断。
|
||
- redirect 响应不被当成成功。
|
||
- DELETE gateway id path escape/decode。
|
||
|
||
集成测试至少覆盖:
|
||
|
||
- Runtime Pod 注册为 `ready`,capacity/used_slots/draining 正确。
|
||
- heartbeat 更新 Pod 状态。
|
||
- metrics 上报后管理端 Runtime Pods 页面能看到指标。
|
||
- 创建 100 个 gateway 端口唯一,第 101 个触发扩容或 no capacity。
|
||
- 重复 create 同一 `instance_id + generation` 不重复启动。
|
||
- gateway report 旧 generation 不覆盖新 generation。
|
||
- ClawManager 通过 proxy 能访问 gateway 页面和 WebSocket。
|
||
- 通过 ClawManager proxy 访问时不要求用户手工粘贴 gateway token。
|
||
- Pod drain 后拒绝新 create,已有 gateway 持续上报,DELETE 后释放端口。
|
||
- Pod 重启后不误报旧 gateway running。
|
||
- instance agent register/heartbeat/state/commands/skills 全链路可用。
|
||
- 日志中没有 token 和 API key。
|
||
|
||
## 16. Do / Don't
|
||
|
||
| Do | Don't |
|
||
| --- | --- |
|
||
| 使用 Kubernetes Service DNS 访问 ClawManager backend | 写死外部 NodePort、`172.16.1.12` 或浏览器 HTTPS 入口 |
|
||
| `POST /v1/gateways` 快速返回 `starting` | 在 HTTP handler 中阻塞等待前台进程退出 |
|
||
| 以 `instance_id + generation` 幂等 | 重试时重复启动多个 gateway |
|
||
| 以真实进程和健康检查决定 `running` | 只读旧状态文件就上报 `running` |
|
||
| JSON merge runtime 配置 | 覆盖用户整个配置文件 |
|
||
| 只转发白名单 env | 把用户请求里的全部 env 原样传给进程 |
|
||
| trusted proxy 模式下通过 ClawManager 自动进入 | 要求用户手工粘贴 gateway token |
|
||
| 检测并上报 clock skew | 忽略时间导致签名和 WebSocket 鉴权问题反复出现 |
|
||
| DELETE gateway 不删除 workspace | 清理进程时顺手删除用户数据 |
|
||
|
||
## 17. 代码位置
|
||
|
||
后端关键实现:
|
||
|
||
- Runtime Pod 上报接口:`backend/internal/handlers/runtime_agent_handler.go`
|
||
- ClawManager 调 agent 客户端:`backend/internal/services/runtime_agent_client.go`
|
||
- Runtime 调度器:`backend/internal/services/runtime_scheduler.go`
|
||
- Runtime Deployment 构建:`backend/internal/services/k8s/runtime_deployment_service.go`
|
||
- Runtime 常量与路径:`backend/internal/services/runtime_capacity.go`
|
||
- 实例 agent handler:`backend/internal/handlers/agent_handler.go`
|
||
- 实例 agent service:`backend/internal/services/instance_agent_service.go`
|
||
- 实例命令 service:`backend/internal/services/instance_command_service.go`
|
||
- 实例 runtime 状态 service:`backend/internal/services/instance_runtime_status_service.go`
|
||
|
||
相关文档:
|
||
|
||
- `docs/clawmanager-agent-v2-contract.md`
|
||
- `docs/runtime-agent-integration-guide.md`
|
||
- `docs/hermes-lite-pro-agent-development.md`
|
||
- `docs/hermes-runtime-agent-development.md`
|
||
- `docs/skill-content-md5-spec.md`
|