# 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` 反复出现。 |