# Agent Runtime 开发规范

本文是 ClawManager 托管 runtime 的 agent 侧开发规范，适用于 OpenClaw、Hermes，以及后续新增的任意 runtime。开发新 runtime 时，应先满足本文的通用要求，再补充 runtime 自己的启动命令、配置文件和健康检查细节。

相关协议和背景请同时参考：

- `docs/clawmanager-agent-v2-development-guide.md`
- `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`

## 1. 范围和目标

一个托管 runtime 镜像不能只启动业务进程。它必须内置一个常驻 runtime agent，由 agent 在 Pod 内负责：

- Runtime Pod 注册、heartbeat、metrics、gateway 状态上报。
- 接收 ClawManager 下发的 create、delete、drain、restart 等控制指令。
- 管理本 Pod 内多个 gateway 子进程。
- 管理端口池、进程、workspace、UID/GID、资源限制和健康检查。
- 把 ClawManager 注入的 AI Gateway、实例 token、代理信任和 runtime 配置写入每个实例自己的 workspace。

一句话原则：agent 是 Pod 内 gateway 管理器，不是同步启动脚本。

## 2. 架构约束

每个 Runtime Pod 内只能有一个 runtime agent 控制进程。一个用户实例对应一个 gateway 子进程和一个 workspace。

```mermaid
flowchart LR
  CM["ClawManager Backend"]
  POD["Runtime Pod"]
  AG["runtime-agent"]
  GW1["gateway: instance 101"]
  GW2["gateway: instance 102"]
  WS["/workspaces shared volume"]

  CM -- "POST /v1/gateways" --> AG
  CM -- "DELETE /v1/gateways/{id}" --> AG
  CM -- "POST /v1/drain" --> AG
  AG -- "register / heartbeat / metrics / gateways report" --> CM
  AG --> GW1
  AG --> GW2
  GW1 --> WS
  GW2 --> WS
```

浏览器只访问 ClawManager 的 HTTPS 入口。ClawManager 再反代到 Pod 内部 gateway。runtime agent 不应把每个 gateway 的自签 HTTPS 地址直接暴露给前端。

## 3. 必须遵守的原则

| 原则 | 要求 |
| --- | --- |
| 异步创建 | `POST /v1/gateways` 必须快速返回，通常 1 到 3 秒内返回 `status=starting`，不能阻塞等待业务 gateway 完全启动。 |
| 幂等创建 | 以 `instance_id + generation` 为主要幂等键，重复请求必须返回同一个 gateway 元数据，不能重复启动进程。 |
| 状态以真实进程为准 | `running` 只能在进程存在且健康检查通过时上报，不能只读旧缓存。 |
| 端口统一管理 | 端口由 agent 本地分配器加锁分配，启动前检查监听占用，停止后释放。 |
| 配置按 workspace 隔离 | 每个实例的 runtime 配置写入自己的 workspace，不能写全局配置。 |
| 不覆盖用户配置 | 修改 runtime 配置必须做 JSON merge，保留已有字段，数组 append 后去重。 |
| 不写死环境地址 | 不写死外部 IP、域名、CIDR，必须从 env 或 ClawManager 请求中读取。 |
| 内部服务优先 | runtime 内部访问 ClawManager 使用 Kubernetes Service DNS，不使用浏览器访问的外部地址。 |
| 时间必须可信 | token、设备签名、JWT 和 WebSocket 校验依赖时间，agent 必须检测时钟偏移，不能在明显偏移时报告健康。 |
| 安全默认开启 | 控制接口校验 token，路径校验不能越权，命令执行不能拼 shell 字符串。 |

## 4. Runtime Pod 环境变量

agent 至少支持以下环境变量：

| 变量 | 说明 |
| --- | --- |
| `CLAWMANAGER_RUNTIME_TYPE` | runtime 类型，例如 `openclaw`、`hermes`。 |
| `RUNTIME_WORKSPACE_ROOT` | workspace 根目录，默认 `/workspaces`。 |
| `RUNTIME_GATEWAY_PORT_START` | gateway 端口起始值，默认 `20000`。 |
| `RUNTIME_GATEWAY_PORT_END` | gateway 端口结束值，默认 `20099`。 |
| `RUNTIME_AGENT_CONTROL_TOKEN` | ClawManager 调 agent 控制接口使用。 |
| `RUNTIME_AGENT_REPORT_TOKEN` | agent 上报 ClawManager 使用。 |
| `CLAWMANAGER_BACKEND_URL` | ClawManager backend 内部地址，推荐 `http://clawmanager-gateway.clawmanager-system.svc.cluster.local:9001`。 |
| `RUNTIME_AGENT_LISTEN_ADDR` | agent 控制接口监听地址，默认 `0.0.0.0:19090`。 |
| `RUNTIME_AGENT_PUBLIC_PORT` | 注册给 ClawManager 的 agent 端口，默认 `19090`。 |
| `CLAWMANAGER_RUNTIME_IMAGE_REF` | 当前 runtime 镜像标识。 |
| `POD_NAME` / `POD_NAMESPACE` / `POD_IP` / `NODE_NAME` | 通过 Downward API 注入的 Pod 身份。 |
| `CLAWMANAGER_TRUSTED_PROXY_CIDRS` | ClawManager app 或 gateway Pod 的来源网段，供 runtime 的 trusted proxy 配置使用。 |
| `CLAWMANAGER_CONTROL_UI_ORIGIN` | ClawManager 内部 Control UI origin，默认可由 `CLAWMANAGER_BACKEND_URL` 推导。 |

要求：

- `CLAWMANAGER_BACKEND_URL` 必须是内部服务地址，不能使用 `https://172.16.1.12:39443` 这类浏览器外部入口。
- 只有对浏览器生成页面 URL 时才使用外部入口。runtime gateway 的信任来源和 LLM base URL 都应使用内部服务 DNS。
- agent 启动时要校验必填 env，缺失时上报 `unhealthy`，并在 `/v1/health` 返回 503。

## 5. Gateway 创建协议

ClawManager 调用：

```http
POST /v1/gateways
X-ClawManager-Control-Token: ${RUNTIME_AGENT_CONTROL_TOKEN}
Content-Type: application/json
```

请求字段以 `docs/clawmanager-agent-v2-contract.md` 为准，当前应支持：

```json
{
  "instance_id": 123,
  "user_id": 45,
  "agent_type": "hermes",
  "workspace_path": "/workspaces/hermes/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",
  "instance_id": 123,
  "port": 20017,
  "pid": null,
  "status": "starting",
  "workspace_path": "/workspaces/hermes/user-45/instance-123"
}
```

创建流程：

1. 校验控制 token、`agent_type`、`instance_id`、`user_id`、`generation`、`workspace_path`、端口范围。
2. 检查当前 Pod 是否 draining。draining 时返回 409 或 503。
3. 按 `instance_id + generation` 查询本地状态。已存在 `starting` 或 `running` 时直接返回已有元数据。
4. 若收到更高 generation，停止并释放同实例旧 generation gateway。
5. 加锁分配端口，先标记 reserved。
6. 创建 workspace 和 home 目录，设置 owner 为请求中的 `uid/gid`。
7. 写入或合并 runtime 配置。
8. 记录 gateway 元数据为 `starting`。
9. 后台启动进程，立即返回。
10. 后台健康检查通过后，上报 `/api/v1/runtime-agent/gateways/report`，状态为 `running`。
11. 启动失败或健康检查失败时，上报 `error`，释放端口，并保留错误信息。

禁止事项：

- 禁止在 HTTP handler 里直接运行前台命令并等待它退出。
- 禁止同一个 create 请求触发多个 gateway 进程。
- 禁止把请求里的任意字符串拼接成 shell 命令。
- 禁止创建不在端口范围内的监听端口。

## 6. 端口和资源管理

agent 必须实现并发安全的端口分配器：

```text
reserve(instance_id, generation, count) -> port_set
commit(instance_id, generation, port_set)
release(instance_id, generation)
list_used()
```

要求：

- 分配时持有互斥锁。
- 在锁内检查 agent 内存状态和系统监听端口。
- runtime 如果需要主端口、control 端口、browser 端口或 sidecar 端口，必须一次分配一组端口。
- 启动失败、健康检查失败、DELETE 成功后必须释放端口。
- `used_slots` 来自真实 `starting` 和健康 `running` gateway 数量，不能来自旧缓存。

资源隔离要求：

- 每个 gateway 使用独立进程组。
- 业务进程最终以请求里的 `uid/gid` 运行。
- CPU、Memory 使用 cgroup 限制。
- Disk 使用 filesystem quota 或周期扫描加错误上报。
- 删除 gateway 不删除 workspace。

## 7. Workspace 和配置写入

workspace 固定格式：

```text
/workspaces/{runtime}/user-{user_id}/instance-{instance_id}
```

agent 必须：

- 对 workspace root 和请求路径执行 realpath。
- 拒绝 `..`、符号链接逃逸、跨用户或跨实例路径。
- 创建 `${workspace}/home`，并设置 `HOME=${workspace}/home`。
- 密钥、token、provider 配置文件权限建议 `0600`。
- owner 设置为该 gateway 的 `uid/gid`。

runtime 配置必须写在 workspace 内。例如：

| Runtime | 推荐配置路径 |
| --- | --- |
| OpenClaw | `{workspace}/home/.openclaw/openclaw.json` |
| Hermes | `{workspace}/home/.hermes/hermes.json` 或 Hermes 原生配置路径 |

JSON merge 规则：

- 对象字段递归 merge。
- 已存在字段默认保留。
- 平台必须控制的字段可以覆盖，但要在代码中显式列出。
- 数组字段 append 后去重，例如 `allowedOrigins`。
- 不删除用户已有的未知字段。
- 写入前先生成临时文件，再原子 rename。

## 8. AI Gateway 和模型凭证注入

ClawManager 会在 `POST /v1/gateways` 的 `environment` 字段中下发实例级 LLM 环境变量。agent 必须把这些变量传给 gateway 子进程，并按 runtime 需要写入配置文件。

允许转发给子进程的变量应使用白名单：

```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
```

要求：

- `CLAWMANAGER_LLM_BASE_URL` 使用 ClawManager 内部服务 DNS。
- `CLAWMANAGER_LLM_API_KEY` 和 `OPENAI_API_KEY` 使用当前实例 token。
- 不能要求用户在 runtime 页面手工填写 OpenAI key。
- 不能把 API key、实例 token、bootstrap token 打到日志。
- 如果 `environment` 里缺少 API key，agent 不应报告 gateway `running`，应上报 `error`，错误消息明确说明缺少实例 LLM token。

Hermes 落地时应把上述 env 转换成 Hermes 的 provider 配置，推荐语义：

```json
{
  "models": {
    "providers": {
      "auto": {
        "type": "openai-compatible",
        "baseUrl": "${CLAWMANAGER_LLM_BASE_URL}",
        "apiKey": "${CLAWMANAGER_LLM_API_KEY}"
      }
    },
    "primary": "auto/auto"
  }
}
```

如果 Hermes 使用不同配置 schema，也必须满足同样的安全和可用性要求：通过 ClawManager AI Gateway 访问模型，不在镜像内写死厂商 key。

## 9. 代理信任和浏览器访问

runtime gateway 在 Pod 内部推荐使用 HTTP。浏览器访问链路应为：

```text
Browser HTTPS -> ClawManager HTTPS -> ClawManager backend proxy -> Runtime Pod HTTP gateway
```

agent 写入 runtime 配置时必须配置：

- Control UI 的 `basePath` 为 `/api/v1/instances/{instance_id}/proxy`。
- 允许的 origin 使用 ClawManager 内部 origin，例如 `http://clawmanager-gateway.clawmanager-system.svc.cluster.local:9001`。
- trusted proxies 使用 `CLAWMANAGER_TRUSTED_PROXY_CIDRS` 或等价 env，不写死 `100.68.x.x`。
- 不使用外部地址 `https://172.16.1.12:39443` 作为 runtime 内部信任来源。

OpenClaw 参考配置：

```json
{
  "gateway": {
    "auth": {
      "mode": "trusted-proxy"
    },
    "controlUi": {
      "allowedOrigins": [
        "http://clawmanager-gateway.clawmanager-system.svc.cluster.local:9001"
      ],
      "basePath": "/api/v1/instances/123/proxy",
      "dangerouslyDisableDeviceAuth": true
    },
    "trustedProxies": [
      "100.68.0.0/16"
    ]
  }
}
```

说明：

- `dangerouslyDisableDeviceAuth` 只能在 ClawManager trusted proxy 模式下使用。
- 如果 runtime 不支持 trusted proxy 模式，必须明确实现一种等价机制，保证浏览器通过 ClawManager 代理即可自动进入，不需要用户手工粘贴 gateway token。
- 如果出现 `origin not allowed`，优先检查 `allowedOrigins` 是否使用内部 origin，以及 WebSocket 代理是否正确传递 Origin。
- 如果出现 `Proxy headers detected from untrusted address`，优先检查 trusted proxies CIDR。

## 10. 时间同步规范

runtime 侧不能忽略时间问题。以下问题通常与时钟偏移有关：

- `device signature expired`
- token 未过期但被拒绝。
- JWT、签名 URL、WebSocket 鉴权异常。
- ClawManager 显示状态和 gateway 实际状态不一致。

agent 必须实现：

- 注册和 heartbeat 响应如果带 `server_time`，记录本地时间与服务端时间偏移。
- 若偏移超过 30 秒，在日志和 health 中上报 `clock_skew_warning`。
- 若偏移超过 120 秒，不应报告 `ready` 或 gateway `running`，应上报 `unhealthy` 或 `error`。
- 生成或校验任何带时间的签名时使用 UTC。
- 不在容器里自行修改系统时间。时间同步由 Kubernetes 节点的 NTP、chrony 或宿主机配置负责。

运维要求：

- 所有 Kubernetes 节点必须启用 NTP 或 chrony。
- 发现签名过期类错误时，先比较浏览器机器、ClawManager Pod、runtime Pod、Kubernetes Node 的 UTC 时间。
- agent 健康检查输出应包含当前本地时间、最近服务端时间和估算偏移，便于排查。

## 11. 状态上报和恢复语义

Runtime Pod 注册 payload 必须包含容量：

```json
{
  "runtime_type": "hermes",
  "capacity": 100,
  "used_slots": 12,
  "available_slots": 88,
  "state": "ready",
  "draining": false
}
```

Gateway report 每个 gateway 建议包含：

```json
{
  "instance_id": 66,
  "gateway_id": "gw-66-7",
  "gateway_port": 20000,
  "gateway_pid": 1234,
  "state": "running",
  "generation": 7,
  "workspace_path": "/workspaces/hermes/user-1/instance-66",
  "started_at": "2026-06-03T06:00:00Z",
  "updated_at": "2026-06-03T06:00:10Z",
  "error_message": ""
}
```

状态规则：

- `starting`：进程准备中或健康检查尚未通过。
- `running`：进程存在、端口监听、HTTP 健康检查通过、必要的代理和 LLM 检查通过。
- `error`：启动失败、配置失败、健康检查失败、凭证缺失或端口冲突。
- `stopped`：进程已停止，端口已释放。
- `unhealthy`：进程存在但健康检查失败。

Pod 重启或升级时：

- agent 启动后先初始化 workspace root、端口池、控制服务和报告客户端。
- 控制服务可用后注册 Runtime Pod。
- 只有初始化完成后才能上报 `ready`。
- 不要把旧 Pod 的 gateway 当成运行中，除非进程确实存在且健康。
- 收到 SIGTERM 时进入 draining，拒绝新 create，尽量上报最后状态。
- 支持 ClawManager 下发 drain、stop、restart gateway 指令。

## 12. 健康检查

`/v1/health` 只有在 agent 能接受控制指令时才返回 2xx。以下情况必须返回 503：

- 必填环境变量缺失。
- workspace root 不可访问。
- 端口池不可用。
- report token 或 control token 缺失。
- 时钟偏移超过健康阈值。
- agent 正在关闭且不再接受请求。

gateway 从 `starting` 转为 `running` 前至少校验：

- 端口已监听。
- HTTP `/` 或 runtime 约定健康端点可访问。
- 代理 base path 配置已写入。
- 使用 ClawManager 内部 Origin 的 Control UI 或 WebSocket 握手不再返回 `origin not allowed`。
- runtime 如果需要 LLM，使用实例 token 调用 ClawManager AI Gateway 可以返回模型列表或完成一次轻量探测。

健康检查失败时不要上报 `running`。应上报 `error`，并带短错误文本。

## 13. 安全规范

agent 控制接口必须校验：

- `X-ClawManager-Control-Token`。
- `instance_id`、`user_id`、`generation` 类型和值范围。
- `agent_type` 必须等于当前 Pod runtime type。
- `workspace_path` 不越权。
- 端口只能来自允许范围。
- `environment` 只允许白名单 key。

进程启动必须使用 argv 数组，不使用 shell 拼接：

```text
execve(binary, ["hermes", "gateway", "run", "--port", "20017"], env)
```

禁止：

- 把请求参数拼成 `sh -c`。
- 日志打印 token、API key、channel secret。
- 允许用户覆盖 agent 自己的控制 token 和上报 token。
- 删除 gateway 时顺手删除 workspace。
- 为了调通临时写死 IP、CIDR 或 `allowedOrigins=["*"]`，除非明确是本地开发环境。

## 14. Hermes Runtime 开发落地清单

开发 Hermes runtime 时，至少完成以下事项：

| 项目 | 要求 |
| --- | --- |
| Runtime 类型 | `CLAWMANAGER_RUNTIME_TYPE=hermes`，注册和上报 payload 中都使用 `hermes`。 |
| Agent 控制服务 | 监听 `0.0.0.0:19090`，实现 `/v1/health`、`/v1/gateways`、`DELETE /v1/gateways/{id}`、`/v1/drain`。 |
| Gateway 启动 | 后台启动 Hermes gateway，使用实例 workspace、端口、UID/GID、HOME。 |
| 配置文件 | 写入 `{workspace}/home/.hermes/hermes.json` 或 Hermes 原生配置，必须 JSON merge。 |
| AI Gateway | 将 `CLAWMANAGER_LLM_*` 和 `OPENAI_*` 注入 Hermes provider 配置。 |
| 代理路径 | Hermes UI 或 WebSocket 必须支持 `/api/v1/instances/{instance_id}/proxy` base path。 |
| Trusted proxy | 信任 ClawManager 内部 proxy 来源，不信任任意外部来源。 |
| 自动登录 | 浏览器通过 ClawManager proxy 进入时不要求手工粘贴 token 或重复确认自签证书。 |
| 端口组 | 如果 Hermes 除主端口外还有 control/browser/sidecar 端口，按端口组分配。 |
| Skills | skill inventory 和状态上报按 `instance_id`、`workspace_path` 分组。 |
| 时间 | 实现 clock skew 检测；偏移过大时不报告 ready/running。 |
| 验收 | 100 个实例并发创建无端口冲突，Pod drain 和重启后状态正确。 |

Hermes 启动命令由 Hermes 项目确定，但 agent 必须保证：

- 命令参数固定且可审计。
- 端口和 workspace 只能来自 agent 校验后的值。
- LLM、proxy、auth 配置在进程启动前已经写入。
- `POST /v1/gateways` 返回时进程可以仍在启动，但 metadata 必须已经持久化。

## 15. 测试和验收

单元测试至少覆盖：

- create gateway 幂等。
- 高 generation 替换低 generation。
- 端口分配并发无冲突。
- 端口被系统占用时跳过。
- workspace path 越权被拒绝。
- JSON merge 保留用户字段，数组去重。
- LLM env 白名单转发。
- 缺少 LLM token 时 gateway 不报告 running。
- trusted proxy 和 allowed origin 配置生成。
- clock skew 阈值判断。

集成测试至少覆盖：

- Runtime Pod 注册为 `ready`，capacity、used_slots、available_slots 正确。
- 创建 100 个 gateway，端口唯一，状态最终为 running。
- 重复创建同一个 `instance_id + generation` 不重复启动进程。
- ClawManager 反代 WebSocket 不出现 `origin not allowed`。
- 通过 ClawManager proxy 进入 runtime 不要求手工粘贴 gateway token。
- runtime 内模型调用经过 ClawManager AI Gateway，不出现 `No API key found`。
- Pod drain 后拒绝新 create，已有 gateway 持续上报，DELETE 后释放端口。
- Pod 重启后不误报旧 gateway running。

上线前 Kubernetes 验证建议：

```bash
kubectl -n clawmanager-system get pods -l app.kubernetes.io/component=runtime-agent -o wide
kubectl -n clawmanager-system logs deploy/hermes-runtime --tail=200
kubectl -n clawmanager-system exec deploy/hermes-runtime -- curl -fsS http://127.0.0.1:19090/v1/health
kubectl -n clawmanager-system exec deploy/hermes-runtime -- date -u
kubectl -n clawmanager-system exec deploy/clawmanager-app -- date -u
```

如果出现认证或签名问题，排查顺序：

1. 时间偏移。
2. 实例 token 是否下发到 `environment`。
3. runtime 配置文件是否包含 provider 和 API key。
4. ClawManager 内部 LLM base URL 是否可达。
5. allowed origins 和 trusted proxies 是否使用内部服务地址和 env CIDR。
6. gateway 是否真正健康，而不是缓存状态误报。

## 16. Do / Don't

| Do | Don't |
| --- | --- |
| 用内部 Kubernetes Service DNS 配置 runtime 到 ClawManager 的访问。 | 写死 `172.16.1.12`、NodePort、外部 HTTPS 入口。 |
| `POST /v1/gateways` 快速返回 `starting`，后台拉起进程。 | 在 HTTP 请求里阻塞等待 `gateway run` 前台进程。 |
| 按 `instance_id + generation` 幂等。 | 重试时重复启动多个 gateway。 |
| 上报状态前检查真实进程和健康。 | 只读旧状态文件就上报 `running`。 |
| JSON merge runtime 配置。 | 覆盖用户的整个配置文件。 |
| 白名单转发 env。 | 把用户请求里的所有 env 原样传给进程。 |
| 使用 trusted proxy 让浏览器通过 ClawManager 自动进入。 | 要求用户手工粘贴 gateway token。 |
| 检测并上报 clock skew。 | 忽略时间，导致 `device signature expired` 反复出现。 |
