Agent Node
Agent Node 是 CommHub 与模型 runtime 之间的执行进程。它负责连接 Hub、接收任务、调用选定 runtime、回传结果,并维护节点日志、会话和定时目标。
Runtime 的安装、鉴权和能力差异以 Runtime 对比表为准;完整命令以 CLI 参考为准。本页只讲节点本身的通用行为。
安装并启动
需要 Node.js ≥ 22.13 和 Bun:
npm install -g bun @sleep2agi/agent-network @sleep2agi/agent-node先按上手指南启动并登录 Hub,然后在准备让 Agent 工作的项目目录中:
anet node create my-agent
anet node start my-agentnode create 会让你选择 runtime,并为节点向 Hub 注册独立身份。node start 默认前台运行;日志出现 SSE connected 后才表示任务推送链路已连通。
常用管理命令:
anet node ls
anet info my-agent
anet logs my-agent --follow
anet node stop my-agent工作目录很重要
Agent 的文件工具以启动目录为工作区。请在目标项目目录创建、启动节点,不要从 $HOME 或包含其他项目/凭据的目录运行。需要后台运行时使用:
anet node start my-agent --tmux从终端启动会 attach 到 tmux;用 Ctrl-B D detach。无 TTY 的环境会以 detached 模式启动。长期守护和开机恢复见干净服务器部署 · 持久化。
选择 Runtime
不要在本页根据旧版本号或固定数量选 runtime。当前可安装组合由 npm 发布频道决定:
- Claude Code、Claude Agent SDK、Codex SDK、Grok ACP 等路径见 Runtime 对比。
- Codex TUI 共存是 preview 功能,必须使用
--copresence的完整启动/恢复路径,见 Codex TUI 共存。 - OpenCode 当前是任务 runtime,不是共享 TUI。
- Grok 共享 TUI 尚未发布;当前可用的
grok-build-acp不支持 attach,见 Grok 节点。
切换 runtime 前先停止旧进程。不要让两个不同进程以同一 alias / node identity 同时连接 Hub。
节点文件
每个项目的节点状态位于 .anet/nodes/<alias>/:
| 路径 | 用途 |
|---|---|
config.json | Hub、runtime、node identity、token、model、flags |
.env | 可选 secret;明文文件,权限应为 0600 |
logs/ | 运行日志。本机看用上面的 anet logs;从客户端看见 节点运行日志 |
goals.json | 本节点的循环任务状态 |
启动时的环境变量优先级
agent-node(包括直接 agent-node --config <path> 启动)按下面的顺序取值,高者胜(实现见 node-secrets.ts 的 loadNodeSecrets;文件顶部注释也写着这三层):
- 进程启动时已经存在的环境变量(
KEY=空串也算已设置,会被保留); config.json的env里的键(明文或_envRef);- 节点目录下的
secrets.env(anet node secret set写的那份)——只填空位,不会覆盖前两层。
secrets.env 的约束:必须 0600 的普通文件且属于当前用户;符号链接、非普通文件、权限不对时整份不生效(不是部分生效)。保留键(PATH、LD_*、NODE_* 等)会被跳过。文件不存在是正常状态。
Hub 金库不是上面的第四层。list_network_secrets 和 GET /api/secrets 只返回名字;agent-node --config 启动时不会向金库要明文。create_node 的 env_refs 目前也不读金库,所以「令牌页上有这个名字」不等于这个进程里有这个值。
Hub 下发的密钥要先落成本地文件,再在下次完整启动时按第 3 层补空位(不会覆盖已经存在的进程环境,也不会覆盖 config.json 的 env):
- 节点私有通道写入
node-secrets.json; - Daemon 范围密钥写入
scoped-secrets.json,节点同名值优先; - 创建节点时的
env_blob写成secrets.env。
这三份都只在下次完整启动时读入。单独重启 Bridge 不会让 TUI 或 server 看到新值。回执和 list 只出 key 名。英文同一节:Environment precedence at startup。 全局的用户登录与当前 network 位于 ~/.anet/config.json。不要提交项目 .anet、token 或 .env。
envRef 让 config.json 只保存环境变量引用;实际值仍可能存入节点的 mode-0600 .env。迁移前先检查备份和目标变量:
anet node migrate-token-to-envref my-agent完整 token 边界见 Token 与权限,配置安全见安全设计。
密钥文件:谁是权威、怎么备份、怎么轮换
权威边界——这三份本地文件是运行时的权威来源,Hub 金库只是投递通道:
| 文件 | 位置 | 权威范围 |
|---|---|---|
secrets.env | <节点目录>/secrets.env(config.json 旁边,node-secrets.ts:42 定义路径) | 该节点自己的环境变量;启动时只填空位(node-secrets.ts:155 loadNodeSecrets) |
node-secrets.json | 节点私有通道写入 | 节点私有密钥(同名时优先于 daemon 范围的值) |
scoped-secrets.json | daemon 目录(0600、revision CAS) | 该 daemon 下的子节点共享值 |
读入规则是整份生效或整份拒绝:secrets.env 若是指向别处的符号链接、非普通文件、属主不是当前用户、或权限不是 0600,整份不生效(不是跳过坏行),文件不存在则是正常状态。
备份(自己做,产品不会自动备份):
- 备份
secrets.env/node-secrets.json/scoped-secrets.json三个文件,权限保持0600、属主为运行用户; - 存到与节点目录不同的位置(同盘同目录的副本在磁盘损坏时一起丢),并且不要进 git;
- 备份里是明文值,等同凭据:按密钥对待(不贴聊天、不进工单)。
恢复:
- 停掉节点/daemon(完整停止,不是只重启 bridge);
- 把备份文件放回原路径,
chmod 600、确认属主; - 完整启动一次——三份文件只在完整启动时读入;
- 用
anet node secret list <node>核对键名齐全(值不会回显)。
轮换顺序(避免中途失效):
- 先写新值(
anet node secret set <node> <KEY>,值走 stdin 或--from-env,不会进 shell 历史); - 完整重启节点,确认新值生效;
- 再在源头吊销旧值;
- 最后更新备份。
回滚条件:新值写入后节点无法启动、或任务开始成片失败且错误指向该密钥时,放回上一份备份并完整重启;scoped-secrets.json 有 revision CAS,两次写冲突以 CAS 报错为准,不要手改文件。
团队规则(可选):ANET_TEAM_RULES
网络 owner/admin 可以在 Hub 上写一份全网规则(GET/PUT /api/networks/:id/rules)。默认不会自动写进节点;要让某个节点采用它,在该节点的环境里设 ANET_TEAM_RULES=1,然后完整重启:
# 先备份(块会加在文件最前面)
cp AGENTS.md AGENTS.md.bak-$(date +%Y%m%d-%H%M%S) # claude 节点是 CLAUDE.md
ANET_TEAM_RULES=1 anet node start <alias> # 或在你自己的启动器里带这个环境变量启动日志会有一行 [team-rules] injected|unchanged|skipped|error …。文件顶部出现这样的块:
<!-- ANET:TEAM-RULES v3 BEGIN — 由 CommHub 自动维护,块外内容不会被修改 -->
## 团队规则(CommHub 自动维护)
### 来源:Hub / 网络 net_xxx(revision 3,更新于 …)
…Hub 上的正文…
<!-- ANET:TEAM-RULES v3 END -->行为约定:
| 情况 | 结果 |
|---|---|
| 块外内容 | 逐字节保留,任何情况下都不改写 |
| 同一 revision 再次启动 | unchanged,文件字节不变(不产生 diff) |
| Hub 不可达 / 非 2xx | 文件原样 + 告警;不会删块,也不阻塞启动 |
| 文件里标记残缺(只有 BEGIN 或版本不一致) | 拒绝写入并报原因,文件不动 |
没设 ANET_TEAM_RULES | 零行为差异:不发请求、不碰文件 |
关掉它:去掉环境变量后重启即可;已写入的块不会自动删除(删块等于静默撤回团队规则),需要时手工删掉标记段或还原备份。
边界:这一版只注入 Hub 一层;Daemon 级规则、层间冲突裁决、合并总长上限都还没有实现。规则改动只在节点下次完整启动时生效,不做运行中推送,也没有客户端编辑页。
想知道某个已发布版本是否真的具备这个行为,可以跑:
bash scripts/verify-team-rules-artifact.sh 2.5.0-preview.156待具名确认:恢复来源指谁(本机备份 / 外部 KMS / 人工重录)——这一条需要人给一句话才能写成确定流程,本文暂按"本机备份 + 人工重录"描述。
不要复制节点身份
跨机器部署时,应在目标机器登录并重新运行 anet node create。复制 config.json 会同时复制 node_id 和 ntok_,可能造成身份、心跳和 SSE 路由冲突。
同一台机器上要复制节点,用 anet node clone / anet node codex fork,见 复制节点(clone / fork)与清理。
任务处理
CommHub ──SSE task──▶ Agent Node ──▶ Runtime / model
▲ │
└──────────── task result ─────────────┘节点只把任务类事件交给模型:
send_task:需要执行的任务,会触发 runtime。send_reply:任务结果,不再次触发模型。send_message:普通消息,不再次触发模型。
这种区分避免 Agent 因回复彼此触发无限循环。状态转换、父子任务和超时语义见任务生命周期。
codex-app-server 节点一次只跑一个 turn,后来的任务排队。从 runtime 2.5.0-preview.141 / 配套 CLI 2.3.0-preview.175 起,经 Hub 认证的 Dashboard 聊天请求默认可在运行时排队最多 4 小时;节点、定时任务、旧来源或未认证请求仍为 30 分钟。Hub 的到期、取消和权限检查仍生效,可能在运行时期限之前拒绝执行。
从 runtime 2.5.0-preview.142 / 配套 CLI 2.3.0-preview.176 起,认证 Dashboard 消息在收件分发和 bridge 的等待队列中排在机器待办之前,同类请求仍按到达顺序处理。已经执行、领取启动或开始核查 Hub 状态的一项继续运行;分发并发满时要先等一个槽位释放,连续人类消息也可能推迟机器待办,不能承诺立即响应。本片不新增活跃 CommHub 回合注入或队列位置 UI;原有经认证的人类 TUI 回合注入规则不变。
活跃网络回合的人类接续需要独立回执和回复归属;接续契约与当前回退验证已单独记录。当前仍采用上述等待优先规则,契约和测试通过不表示活跃网络回合注入已经实现。
超过相应排队期限仍未开始的任务会以「在队列中等待 N 分钟仍未开始」失败。要显式覆盖这个默认值,在启动节点的环境里设 ANET_QUEUE_TIMEOUT_MS(整数毫秒,例如 6 小时 = 21600000);该配置对两类请求都优先生效,非法值按对应来源默认值回退并警告一次。排队时间不扣除开始处理后的执行时钟。公开新包不代表已有节点已采用,需要原生命周期管理者在正常空闲窗口确认实际版本。
节点启动时要先恢复(resume)它绑定的线程,才开始接任务。线程很大时(rollout 文件上百 MB)这一步可能要几分钟;默认每次最多等 300 秒,超时重试一次,仍失败就先向 hub 报离线再退出,不会改用新线程(那会丢掉对话历史)。同一期限也用于等待新 TUI 线程真正生成 rollout,避免把尚未落盘的 thread id 写成可恢复会话。要改这个上限,设 ANET_CODEX_RESUME_TIMEOUT_MS(整数毫秒,规则同上)。旧版 Codex 会把完整历史生成为一个大响应帧;客户端的有限 maxPayload 只限制接收端,不能限制 Codex 生成这段数据时的内存。因此 Linux 上的恢复阶段另有主机级单路闸门:按 rollout 的 9 倍(最低 4 GiB)预留可用内存,余量不足时排队并打印原因,直到 launcher、bridge 和 TUI 都完成精确线程绑定才释放;持有期间会续租,不会因恢复超过 10 分钟而放进第二个节点。等满 600 秒仍不满足内存要求时,为兼容既有启动流程并避免整批节点永久卡住,闸门只允许一个恢复继续,同时明确 WARN 当前可用量、需求量和 OOM 风险。Windows / macOS 没有这道基于 /proc 和文件租约的保护,启动日志会明确告警。
opencode 节点每个任务默认最多等 30 分钟。共存模式(opencodeMode: "copresence")下这是整个任务的总时长;到点后 bridge 只是停止等待,不会中止共享会话里的这一轮。任务会在节点的 TUI 里继续跑完,发送方收到的回复会写明这一点,但最终结果不会再自动回传。headless 模式下这是「连续没有任何进展输出」的时长,到点会结束 opencode 子进程,这一轮就中止了。要改这个上限,设环境变量 OPENCODE_TIMEOUT_MS,或在节点 config.json 里设 flags.timeout / flags.opencodeTimeoutMs(整数毫秒;0 = 不设上限)。优先级依次是环境变量、flags.timeout、flags.opencodeTimeoutMs。节点启动时会打印一行 [opencode] task timeout=… source=…,写明实际取到的值和来源。
附件、图片和 channel 支持随 runtime 不同;不要假设所有 runtime 都能处理媒体。按 Runtime 对比和 Channel 指南确认。
工具与权限
工具来自两层:runtime 自带工具,以及 Agent Network 注入的 CommHub 工具。--tools 只对支持自定义工具列表的 runtime 生效;不要用它推断 Codex、Claude Code 或 Grok 的实际 sandbox。
anet node create reader --runtime claude-agent-sdk --tools Read,Glob,Grep创建完成后,CLI 会打印行为披露;同时检查节点 config.json 的 permissionMode / dangerouslySkipPermissions / runtime 专用 flags。不同 runtime 默认值并不相同,且 Codex TUI 共存默认只读。
把能够写文件、执行 shell 或联网的节点当作不可信代码:
- 使用独立、可丢弃的工作目录。
- 不把生产密钥放进 Agent 可读目录。
- 只给需要的 Hub/network 权限。
- 修改权限后用一个无害任务验证实际行为,不只看配置名称。
详细威胁模型和默认值见安全设计。
循环任务
对在线节点创建周期任务:
anet node loop my-agent "检查待处理 Issue" --every 5m
anet goal list my-agent
anet goal cancel my-agent <goal-id>Dashboard 的 /goal、/loop 会原样进入目标 runtime/TUI;ANet 周期任务统一使用 /aloop(/agoal 为同义入口,二者都要求间隔)。非 Dashboard 的旧 /goal、/loop 暂时兼容并提示迁移。循环状态保存在该节点的 goals.json;它不是高精度 cron,并会真实消耗模型额度。
完整的路由差异、支持的时间格式、执行/停止规则、自管理工具、重启限制与排查方法见 Goal 与 Loop。
生命周期与恢复
- 启动:读取节点配置,注册/报告状态,连接 SSE。
- 运行:处理任务、定期上报状态;连接中断后按退避策略重连。
- 停止:优先使用
anet node stop <alias>,让节点关闭连接并报告离线。 - 恢复会话:行为随 runtime 不同;先查看
anet info <alias>和对应 runtime 指南,不要手工改 session/thread id。 - 改名:使用
anet node rename,不要直接改目录名、alias 或node_id。
若同一 alias 出现重复结果、runtime 来回变化或状态异常,先检查重复进程:
anet info my-agent
anet logs my-agent --follow
tmux ls停止旧实例后再启动一个。更多现象见故障排查。