# 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-20099` |
| 单 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": 20099
  },
  "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

no free port
```

- 启动失败、健康检查失败、配置写入失败时，上报 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`
