Claude Code CLI 2.1.235 Remote Routines、Runner 运维与通知队列:远端任务怎样被创建、承载并回到主循环¶
版本:
2.1.235| 证据:Static(发布 bundle 的可读逆向视图) | 远端账号、调度服务与真实 runner 接单结果:Boundary
60 秒心智模型¶
读者问题: Claude Code 创建一个定时云端任务后,任务究竟在哪里运行;self-hosted runner 卡住时客户端能做什么;GitHub webhook 或例行任务结果又怎样安全地回到当前对话?
一句话模型: RemoteTrigger 只改远端 routine 控制面,self-hosted runner 工具只观察或操作执行承载面,ReadNotifications 只消费当前会话的外部输入队列;三者通过 session、run 和 notification 标识关联,但没有一个跨三层的原子事务。
贯穿场景:用户创建一个“工作日检查仓库 CI”的 routine。第二天 routine 已生成 run,但承载它的 self-hosted runner 失联;操作者先查看 pool、runner、session 和本机健康状态,再把 session requeue。远端最终投递一条 github_webhook 或 trigger_fire notification,主 Agent 调用 ReadNotifications 读取后继续处理。
图的结论:创建 routine 不等于 run 已创建,run 已创建不等于 runner 已执行,runner 已执行也不等于结果已被当前主循环读取;每一段都有自己的 gate、状态与恢复动作。
场景的前后状态¶
| 对象 | Before | Transformation | After | 用户可见效果 |
|---|---|---|---|---|
| Routine record | 没有 schedule/webhook | RemoteTrigger create 或 create_webhook_trigger 写远端控制面 |
远端返回 routine id、server-parsed next_run_at 或绑定 URL |
用户拿到管理链接和服务端解释后的执行时间 |
| Run session | routine 到点但尚未建 session | 调度服务完成预检并创建 run | list_runs 可看到 session id、状态与时间 |
能继续读取该 run 的日志;空列表仍不能证明从未触发 |
| Runner assignment | session 已分配给失联 runner | 操作者核对 assignment 后执行 requeue |
原 runner 被加入 excluded 集合,session 回到调度队列 | 任务可被其他 runner 领取;这不是对旧副作用的回滚 |
| Local runner process | 本机尚未承载 runner | spawn_local 启动 detached child 并写 PID 文件 |
进程在当前 Claude 会话结束后仍可能继续 | 返回 PID、log path、health port 和等价命令 |
| Notification queue | 外部事件到达但模型未读 | 入站校验、去重、排队、nudge;随后 drain | 已读 ID 保留,pending 缩短,事件 lifecycle 被确认 | 主 Agent 得到按最老优先排列的外部数据 |
| Agent Loop | 正在做原任务 | task-notification meta prompt 要求先 drain | 下一轮吸收 notification body | 模型基于外部事实继续,但 body 不获得更高指令权 |
状态所有权与前后变化¶
| 状态 | 真正 owner | 客户端保存/看到什么 | 谁能改变 | 生命周期边界 |
|---|---|---|---|---|
| Routine 定义、启停、下次执行时间 | claude.ai routine API | HTTP status、JSON、解析后的 schedule summary | RemoteTrigger create/update/run/create_webhook_trigger |
客户端不能证明 scheduler 按时触发 |
| Run session 与 event pages | cloud session/event API | run id、状态、cursor、压缩后的 transcript event | 远端 worker 追加;客户端只读 | list_runs 只有实际建成 session 的 fire |
| Pool、runner、assignment、secret metadata | self-hosted runner control API | aggregate、lease、assignment、secret metadata | 多数工具只读;requeue 写 assignment |
secret value 不返回;服务端冲突保护仍是最终裁决 |
| 本机 runner child | 当前 OS | PID、PID file、log file、health/metrics endpoint | spawn_local 创建;OS/操作者停止 |
detached child 不随当前 Agent Loop 自动结束 |
| Notification pending buffer | 当前 app state | pending[]、drainedIds[]、nudge |
transport arrival 与 ReadNotifications |
仅主会话能 drain;subagent 被拒绝 |
| Notification command lifecycle | 当前 remote session transport | event UUID 的 completed acknowledgement | malformed/duplicate/drain 路径调用 ack | 超大正文和满 buffer 故意不 ack,等待服务端重投 |
| Tool result/transcript | 主 Agent Loop | JSON 或受控文本结果,与 tool_use_id 配对 |
工具映射器写入下一轮消息 | 不能撤销已发生的远端创建、requeue 或本机 spawn |
这里最重要的所有权判断是:operator tool 能看到控制面,不等于 operator tool 拥有执行面。 get_pool 返回 capacity 只是快照;read_health 只探测本机某个端口;list_runs 只列已建 session;ReadNotifications 只消费送达当前会话的队列。它们互相补证,不能互相替代。
完整调用顺序¶
1. 工具装配先决定这三组能力是否进入当前会话¶
RemoteTrigger 不是普通离线工具。2.1.235 同时要求:
- API provider 是
firstParty; - 当前凭据走 claude.ai OAuth 路径并具有对应基础 scope;
- 当前不是
CLAUDE_CODE_REMOTE会话; - feature
tengu_surreal_dali开启; - managed/local policy 允许
allow_remote_sessions; - policy 允许
allow_routines。
任何一项不成立,静态注册对象仍在 bundle 中,但不会成为本轮可用的 RemoteTrigger。
9 个 self-hosted runner operator tools 走另一条 gate。它们只有在单向的 wizardOperatorToolsEnabled latch 被打开后,才由工具池装配器整体加入。这个 latch 是生产环境单向开启:源码明确拒绝把它重新设为 false。因此普通对话看到 runner 实现代码,不代表当前 request advertised 了这 9 个 schema。
ReadNotifications 的注册对象总在基础工具集合附近,但 isEnabled 要求 remote-mode 环境与对应 remote session 能力都成立。它只服务当前 remote 主会话的排队外部输入。
证据: reverse/javascript/cli.readable.js:62747-62749、87233-87236、297687-297690、298543-298553、315665-315675。
2. RemoteTrigger 先把 8 个 action 归一成 method、path 和 body¶
RemoteTrigger 的输入不是八个独立工具,而是一个带 action discriminator 的 operator:
| action | HTTP | path | 必填输入 | 客户端写入性判断 |
|---|---|---|---|---|
list |
GET | /v1/code/triggers |
无 | read-only |
get |
GET | /v1/code/triggers/{trigger_id} |
trigger_id |
read-only |
create |
POST | /v1/code/triggers |
body |
write |
update |
POST | /v1/code/triggers/{trigger_id} |
trigger_id、body |
write |
run |
POST | /v1/code/triggers/{trigger_id}/run |
trigger_id;body 可选 |
write |
create_webhook_trigger |
POST | /v1/code/webhook-triggers |
body |
write |
list_runs |
GET | /v1/code/sessions?trigger_id=... |
trigger_id;cursor 可选 |
read-only |
get_run_log |
GET | /v1/code/sessions/{session_id}/events |
session_id;cursor 可选 |
read-only |
get/update/run/list_runs 缺 trigger_id、get_run_log 缺 session_id、create/update/create_webhook_trigger 缺 body 时,客户端在发请求前直接抛错。run 还会从可选 body 中移除同名 trigger_id,避免 path id 与 body id 形成两个来源。
Auto Mode 下所有 action 都交给 classifier review;非 Auto Mode 的工具 permission 返回 allow,但这不跳过 provider、policy、auth 和 HTTP 错误。
证据: reverse/javascript/cli.readable.js:298514-298608。
3. Routine 请求使用进程内 OAuth,20 秒后终止等待¶
请求使用 auth: "teleport-org",携带本版 beta header,timeout 固定为 20,000 ms,并绑定当前工具的 abort signal。OAuth token 由客户端 transport 在进程内添加,不需要也不应该通过 shell/curl 暴露。
transport 层 ok:false 分成两类用户错误:
no-auth:提示运行/login;- 其他 transport reason:包装成
Remote triggers unavailable: <reason>。
HTTP status 是否成功另算:2xx 才进入成功解析;非 2xx 仍以 {status,json} 进入 operator result,而不是由该分支统一抛成网络异常。这使模型能看到服务端的具体拒绝 body,但也意味着调用工具成功返回不等于业务 action 成功。
证据: reverse/javascript/cli.readable.js:298585-298617。
4. 创建和更新必须把“服务端解释后的时间”返回给用户¶
对 create、update 和成功的 create_webhook_trigger,客户端不会只透传原始 body:
- routine response 尝试解析
id、enabled、next_run_at、cron_expression、run_once_at; next_run_at被格式化成相对时间和标准 UTC 时间;- 若一次性任务的
next_run_at已在过去,追加显式警告; - 若 disabled,显示“next run would be ...”;
- 有 id 时追加 claude.ai
/code/routines/{id}管理链接; - webhook action 不伪造 schedule,而是显示它将触发哪个 routine。
因此用户确认的应是服务端返回的 next_run_at,不是模型自己对 cron 或 timezone 的推算。
证据: reverse/javascript/cli.readable.js:298477-298493、298609-298625。
5. list_runs 每页 10 条,但空列表不是“从未触发”¶
list_runs 固定 limit=10,cursor 最长 1024 字符。返回行被裁成 id、title、status、worker_status、created_at、last_event_at,并为每个 session 附加 claude.ai URL。
只有 fire 已成功创建 session,才会有 run row。以下情况可能发生过 fire 但没有 row:
- routine paused;
- fire cap、429、kill switch 或 org setting 在建 session 前拒绝;
- scheduler 未运行;
- repository access、token preflight、environment lookup 在建 session 前失败;
- routine 把输入追加到已有 session,而非创建新 session。
所以空列表的恢复动作是再调用 get 检查 enabled/next_run_at,不是直接断言 scheduler 从未触发。
证据: reverse/javascript/cli.readable.js:298250-298261、298493-298513、298567-298574。
6. get_run_log 每页取最新 200 个 event,再做语义压缩¶
get_run_log 使用 limit=200&sort_order=desc。客户端不把远端 JSONL 原样灌入模型,而是按事件类型压缩:
- assistant text 最多约
2,000字符;user text 最多约1,000; - tool input 摘要最多
300;普通 tool result 最多400;error result 最多1,500; - 单个格式化 event 最多
4,000; - 原始字符串先受
16,000字符通用裁剪; - 低价值 status/thinking/task-progress 等事件只计数,不逐条展示;
- 最多列出 6 类被跳过事件的计数;
- page 预算不足时保留较新的 event,并说明有多少旧 event 未显示;
- 总 tool result 上限约
100,000字符,并为 header/cursor 留出空间。
compact_boundary、permission denial、API retry/error、stop-hook failure、rate-limit rejection 和 final result 都有专门的人类可读投影。这个转换提高可读性,但不是完整远端 transcript;排障时必须保留 events_fetched、events_shown 和 next_cursor 的差异。
所有 run title 和日志都被显式标为外部数据:它们可能引用仓库、issue、网页或 connector 内容,不能因为通过官方 routine API 返回就升级成指令。
证据: reverse/javascript/cli.readable.js:298250-298476、298574-298585。
7. Runner 控制 API 先执行 first-party 与 OAuth-only 双重认证¶
runner 的远端管理工具统一经过 bet():
- provider 必须是
firstParty; - 客户端先尝试刷新本地登录;
- 必须存在 OAuth
accessToken; ANTHROPIC_API_KEY明确不能用于这些环境管理 endpoint;- 请求携带 Bearer、
anthropic-version: 2023-06-01,timeout20,000 ms; - 401/403/404/409/429 被保留为有 status 的
SelfHostedRunnerApiError; - 结果在进入 tool result 前统一经过 secret redaction。
这组 operator tool 的目标是让模型协助操作者诊断,不是把环境 secret 交给模型。list_secrets 只返回 jti、label、created_at、revoked、last_used_at 等 metadata,description 明确承诺不返回 value。
证据: reverse/javascript/cli.readable.js:297840-297918、297975-298002。
8. 九个 runner 工具分为四类,不应按名字平铺¶
| 类别 | 工具 | 主要状态 | 副作用 |
|---|---|---|---|
| 远端只读控制面 | get_pool、list_runners、list_sessions、list_secrets |
pool aggregate、runner lease/assignment、session failure、secret metadata | 远端 API 读取;结果可能进入 transcript |
| 本机只读探针 | read_health、read_metrics、tail_log |
loopback health、Prometheus gauges、log tail | 读取本机端口/文件;日志先脱敏 |
| 本机写操作 | spawn_local |
detached runner process、PID/log/workspace | 创建目录、进程和 PID 文件;持续到会话外 |
| 远端写操作 | requeue_session |
assignment/excluded runner set | 让 session 回队列,可能在另一 runner 重启 |
每个远端只读结果都附带 equivalent.ui,指向 Admin settings 中相同观察面。这个字段的用途是让操作者可以脱离 Agent 重复验证,不是第二套数据源。
9. spawn_local 是 detached process,批准后不会自动回滚¶
spawn_local 默认:
capacity=1;base_dir=./runner-setup/workspace;health_port=8080;log_path=./runner-setup/runner.log;- PID file 固定为
./runner-setup/runner.pid。
客户端把这些路径解析成绝对路径后创建目录,通过当前 executable 的 self-hosted-runner 子命令启动 child。参数始终使用空格分隔,并显式传 --base-dir,因为 runner 内建 /workspace 默认值不适合操作者笔记本。
child 使用 detached:true、stdio:"ignore"、unref();只有 spawn event 成功且有 PID 才写 PID file。工具返回 {pid,pid_file,log_path,health_port,command}。permission 文案明确说明:runner 会执行该环境排队的 session,并在当前 Claude 会话结束后继续运行。
因此 rewind transcript、取消后续模型调用或关闭 TUI,都不会自动 kill 已 spawn 的 runner。
证据: reverse/javascript/cli.readable.js:298119-298191。
10. Health、metrics 和 log tail 是三种不同证据¶
read_health 与 read_metrics 都只访问 127.0.0.1:{health_port},timeout 固定 2,000 ms:
- port
0返回{disabled:true}; - 连接异常返回
{unreachable:true,error}; - health 请求接受任意 HTTP status 并返回 response body,所以拿到 JSON 不等于 status 是 2xx;
- metrics 只解析
claude_code_self_hosted_runner_*前缀;bucket、session_idle_seconds、poll_errors_total被忽略;同名 gauge 累加;locked_accountlabel 可提取 email; - metrics raw text 在返回前脱敏。
tail_log 默认读取最后 65,536 bytes,不是最后 N 行。它从文件尾按 byte offset 读取,UTF-8 边界可能从半个字符开始;随后应用同一 redactor:URL userinfo、key/token/password、sk-ant、Bearer、JWT、GitHub/GitLab/Slack/Stripe/Meta 等 PAT 形态和 Basic auth 都被替换。脱敏是风险降低,不是对任意未知 secret 格式的数学保证。
证据: reverse/javascript/cli.readable.js:298003-298093、298192-298224。
11. requeue_session 必须经过人工/classifier,并用 assignment conflict 防止误操作¶
requeue_session 是 9 个工具中唯一远端写操作:
isReadOnly=false;- 忽略 whole-tool allow,且禁止生成 always-allow 规则;
- Auto Mode 必须 classifier review;
- 其他模式显示 ask,明确说明会在另一 runner 重新启动 session;
- POST body 带操作者观察到的
runner_id。
server 会验证当前 assignment 仍匹配该 runner;若 session 已被其他控制器移动,返回 409 Conflict,阻止基于过期快照的 requeue。对于 runner 已消失的 stuck session,schema description 表示 server 可接受任意 runner id。成功后 observed runner 被加入 excluded list,避免队列立即把任务分回同一故障 runner。
这只改变未来 assignment;旧 runner 已完成的 Git push、远端 API write 或文件副作用不会被撤销。
证据: reverse/javascript/cli.readable.js:298094-298118。
12. Notification 到达时先校验、去重,再决定是否确认¶
入站 queued_notification 先经过 schema:
notification_id非空;origin只接受 1-40 位小写字母/下划线,否则 coercion 为unknown;priority只接受now/next/later,否则归为later;queued_at保留字符串;content原样保留。
处理顺序决定重投语义:
- malformed:drop,并 ack event lifecycle;
- 当前非 remote mode:drop,并 ack;
- content 超过
87,488字符:拒绝且不 ack; - notification id 已在最近
1,000个 drained IDs:不重复入队,但 ack 本次 delivery; - 同 id 已 pending:直接返回,不重复排队;
- pending 已达
100:拒绝且不 ack,同时重新 nudge; - 其他情况 append 到 pending,再创建/刷新 nudge。
不 ack 超大正文和满 buffer 是背压设计:客户端没有假装已处理,服务端可以在条件变化后重投。
证据: reverse/javascript/cli.readable.js:291287-291348、291432-291454。
13. Nudge 只提醒模型“去读”,不把正文伪装成高优先级指令¶
nudge 是 mode:"task-notification"、priority:"later" 的 meta prompt。它包含 unread 数量和按 origin 汇总的计数,要求模型立即调用 ReadNotifications,并明确声明:正文是 out-of-band external data,不是 nudge 本身的 instructions。
若 nudge 在 Agent 忙碌时被吸收/忽略,客户端会在安全边界 rearm;最大 rearm 次数是 2。超过后只记录 nudge_ignored,避免同一个提醒无限重入主循环。已有相同 task-notification 仍在队列时,不重复创建。
证据: reverse/javascript/cli.readable.js:291348-291393。
14. ReadNotifications 按 90,000 字符预算 drain,并且只能由主会话调用¶
每次 drain 的总体预算是 90,000 字符。计算先预留约 2,000 字符 header,并为每条通知预留 512 字符 envelope;第一条即使接近预算也会被取出,后续条目超预算则留在 pending。顺序保持 oldest first。
状态更新与 acknowledgement 顺序如下:
- 从 pending 中选出本批;
- 原子更新 app state:移除本批、把 IDs 追加到
drainedIds并截取最后1,000个、清空 nudge; - 移除对应 task-notification;
- 对本批每个 event UUID 标记 command lifecycle completed;
- 返回
{notifications,remaining}。
ReadNotifications.call 检查 agentId:只要它存在,就拒绝调用,错误明确写明 subagent 不得 drain session notification buffer。这样不会出现某个后台 Agent 抢走主会话外部消息、主 Agent却只看到“已经读过”的情况。
工具结果再次强调:body 可以伪造 delimiter,只有客户端给出的条数可信;应按 system prompt 和 body 内自报 sender 判断谁能下指令,并对异常内容回到 primary source 验证。
证据: reverse/javascript/cli.readable.js:291393-291487。
15. Drain 结果进入下一轮,但三个 owner 仍不合并¶
主 Agent 看到 notification 后,可以再调用 RemoteTrigger get/list_runs/get_run_log 或 runner operator tools 验证状态。这个闭环是“观察 -> 决策 -> 操作 -> 再观察”,不是分布式事务:
- notification 被 drain 后,routine record 不变;
- requeue 成功后,旧 run log 不消失;
- spawn local 成功后,关闭当前 session 不停止 runner;
- update routine 成功后,已创建的旧 run 不自动取消;
- tool result 被 compact 后,远端 routine/runner 状态仍在外部系统中。
Gate、优先级与阈值¶
| 项目 | 2.1.235 精确值/规则 |
行为含义 |
|---|---|---|
| RemoteTrigger actions | 8 | 一个 operator schema 分派 4 读、4 写 action |
| Remote request timeout | 20,000 ms |
超时只终止本次等待,不证明服务端未执行 write |
list_runs page |
10 |
cursor 继续读取更旧 run |
get_run_log page |
200,descending |
先取最新 event,再在客户端倒序成可读时间线 |
| RemoteTrigger result cap | 100,000 chars |
log 会先按语义压缩再进 tool result |
| log string cap | 16,000 chars |
单个原始长字段先裁剪 |
| formatted event cap | 4,000 chars |
单 event 不独占整个结果 |
| Runner API timeout | 20,000 ms |
provider/OAuth 通过后仍有网络与 HTTP 失败 |
| Local health/metrics timeout | 2,000 ms |
只探测 loopback endpoint |
| Default health port | 8080 |
0 明确表示禁用 |
| Default log tail | 65,536 bytes |
byte tail,不是 line count |
| Notification pending cap | 100 |
满时不 ack,形成背压 |
| Notification drained ID cap | 1,000 |
有界 dedupe history |
| Notification drain budget | 90,000 chars |
单次 tool result 不吞掉全部上下文 |
| Per-notification content cap | 87,488 chars |
90,000 - 2,000 - 512 |
| Nudge rearm cap | 2 |
防止提醒无限驱动 Agent Loop |
| Notification result cap | 100,000 chars |
tool 自己跳过 aggregate budget,但仍有硬 ceiling |
优先级要分三层:notification payload 自带 priority 字段,但当前入队状态保存的是 id/origin/content/time/event UUID;真正推动 Agent Loop 的 nudge 固定为 priority:"later"。因此外部发送者不能仅靠把 payload 写成 now 就绕过主循环的调度和指令层级。
Drop、ack、backpressure 和 drain 是四种不同结果¶
| 到达结果 | 是否入 pending | 是否 ack delivery | 后续含义 |
|---|---|---|---|
| malformed 或当前不在 remote mode | 否 | 是 | 客户端明确丢弃,发送端不应无限重投同一坏 payload |
| ID 已在 pending 或最近 1,000 个 drained IDs | 否/保持原条目 | 是 | 重复 delivery 被吸收,不产生第二条业务消息 |
| content 超过 87,488 或 pending 已满 100 | 否 | 否 | 形成背压,发送端仍需保留并在容量恢复后重投 |
| 合法新消息 | 是 | 到 drain 时才 ack | nudge 只报数量;主 Agent 调用 ReadNotifications 后才完成本次消费确认 |
因此“服务端收到 ack”不等于正文指示的业务动作完成,只说明这次 notification delivery 已被丢弃、去重或真正 drain。排障必须同时看 pending 数、drained ID、transport event UUID 和 routine/runner 的独立业务状态。
失败与恢复¶
| 失败 | Detection | Retry/change | State retained | 最终用户效果 |
|---|---|---|---|---|
| RemoteTrigger gate 未通过 | 工具不进入可用池 | 修登录/policy/feature/entrypoint 后重建工具池 | 已有 routine 不受影响 | 当前会话看不到工具 |
| Routine transport no-auth | ok:false/no-auth |
/login 后重试 |
未证明服务端 action 已发生 | 明确认证错误 |
| Routine HTTP 非 2xx | {status,json} |
按 status/body 修 body、policy 或 quota | 已发生的远端副作用取决于 server | 模型可见原始拒绝信息 |
| 20s timeout/abort | transport 抛错 | 先 get/list 查状态,再决定是否重试 write |
服务端可能已接收请求 | 不能盲目重复 create/run |
list_runs 为空 |
0 rows | get routine,查 enabled/next_run_at |
routine record 保留 | 不误报“从未触发” |
| run log page 太大 | events_shown < events_fetched |
用 cursor 读更旧页;必要时看原始服务面 | run/events 不变 | 得到较新、可读摘要 |
| Runner provider/API key 不合格 | 403/401 preflight | 切回 first-party + claude login |
runner/pool 不变 | operator tool 失败且不泄漏 secret |
| Local health unreachable | 2s exception | tail_log、确认 port/PID/process |
detached process 可能仍活着 | 返回 {unreachable,error} |
| Spawn error | 无 spawn event/PID | 修 executable/path/secret 后重试 | 已创建目录可能保留 | 没有 PID 时不伪报成功 |
| Requeue assignment stale | HTTP 409 | 重新 list session/runner 后再决策 | 新 assignment 保留 | 避免把已移动 session 再次移动 |
| Notification malformed/non-remote | schema/gate | drop 并 ack | 不进入 pending | 主 Agent 永远看不到正文 |
| Notification 太大 | content > 87,488 |
发送端缩短后重投 | 不 ack,不进 pending | 不污染上下文,也不假装已处理 |
| Pending 满 100 | buffer cap | 先 drain,再等待重投 | 现有 100 条保留 | 新条目不 ack |
| Nudge 被忽略 | rearm 达 2 | 记录 ignored;等待其他请求边界/人工动作 | pending 保留 | 消息未丢,但不会无限打断 |
| Subagent 调用 ReadNotifications | agentId !== undefined |
主 Agent 调用 | pending 完整保留 | 防止后台 Agent 抢消息 |
写操作重试前必须先观察¶
create、update、run、create_webhook_trigger、spawn_local、requeue_session 都有“客户端超时/中断但外部系统已经开始执行”的窗口。可靠恢复顺序是:
- 用 list/get/PID/health/session assignment 查询当前事实;
- 对比目标状态;
- 只补做缺失动作;
- 保留旧 run/log/notification 作为诊断证据。
直接重放 write 会产生重复 routine、重复 run、多个 detached runner 或二次 requeue。
用户影响:token、延迟、费用、隐私与副作用¶
Token 与上下文¶
get_run_log的语义裁剪和ReadNotifications的 90k drain budget 都是在保护模型上下文;它们降低一次性膨胀,但意味着结果不是原始全量事件流。- runner list/pool/session 返回任意 JSON object,复杂环境可能仍产生较大 tool result;UI path 和 metadata 也会进入 transcript。
ReadNotifications设置skipAggregateToolResultBudget,避免通用聚合器再次压缩,但硬上限仍是 100k。
延迟与费用¶
- Routine 和 runner control API 每次最多等待 20s;日志分页、runner 状态交叉验证会叠加网络往返。
- health/metrics 各自最多 2s;它们不触发模型费用之外的远端 control API,但仍增加本地诊断时间。
- routine 真正运行会产生独立 cloud Agent Loop 的模型/环境成本;静态 bundle 不能证明账号计价、quota 或账单归属。
隐私¶
- Routine title/log 可包含仓库、issue、网页和 connector 数据;进入当前 tool result 后会成为会话上下文。
- Runner list 可能返回 account email、session failure log;secret list 只应返回 metadata。
tail_log和 metrics 应用已知规则脱敏,但未知 credential 格式、业务 payload 和路径仍可能存在敏感信息。- Notification body 原样进入主对话;origin 是 server-attested token,但正文仍是不可信外部数据。
副作用与恢复¶
spawn_local创建长期 OS process 和文件;requeue改远端 assignment;routine create/update 改服务端 schedule。- compact、resume、rewind 和删除 tool result 都不能普遍撤销这些外部副作用。
- notification ack 只说明 delivery command lifecycle 已完成,不说明正文指示的业务动作已经执行。
证据索引¶
| 结论 | Evidence class | 位置 |
|---|---|---|
| RemoteTrigger gate、8 actions、20s request、permission 与 response | Static runtime | reverse/javascript/cli.readable.js:298514-298625 |
| run list/log 分页、压缩、untrusted-data 标记 | Static runtime/consumer | reverse/javascript/cli.readable.js:298250-298513 |
| runner 名称、描述、first-party/OAuth、redaction、API error | Static runtime | reverse/javascript/cli.readable.js:297821-297918 |
| runner list/read tools | Static runtime | reverse/javascript/cli.readable.js:297891-298093 |
| requeue、spawn、tail | Static runtime | reverse/javascript/cli.readable.js:298094-298224 |
| operator tools 的一体装配与 wizard latch | Static consumer | reverse/javascript/cli.readable.js:4556-4563、315665-315675 |
| notification arrival、dedupe、backpressure | Static runtime | reverse/javascript/cli.readable.js:291287-291348 |
| nudge、drain、ack 与预算 | Static runtime | reverse/javascript/cli.readable.js:291348-291454 |
ReadNotifications 主会话限制与 result security wording |
Static runtime/consumer | reverse/javascript/cli.readable.js:291454-291487 |
对应结构化 claim:remote-ops.*,见 analysis/mechanism-evidence.jsonl。
Boundary:本版不能证明什么¶
- bundle 能证明客户端 gate、请求 path/body、timeout、结果压缩和错误映射,不能证明某账号当前拥有 routines、remote sessions 或 self-hosted entitlement。
create返回 2xx 与next_run_at不能证明 scheduler 将准时触发;真实时钟、fire cap、kill switch 和服务端队列属于外部系统。list_runs只证明 API 返回的已建 session;没有 row 不能证明没有 fire。- runner tools 能证明 operator contract,不能证明某 pool、runner 或 secret 真实存在,也不能证明 requeue 后另一 runner 成功接单。
read_health返回 body 不证明 HTTP status 是 2xx;read_metrics只解析当前 loopback response,不证明控制面认为 runner healthy。- redactor 能证明已实现一组模式替换,不能证明任意业务 secret 都会被识别。
- notification 进入 pending 只能证明当前客户端接收路径执行;服务端最初如何鉴权 webhook、保存多久、怎样重投,不在发布物内。
- 这里没有把当前官网行为倒灌成
2.1.235;所有数值都绑定本版静态代码,未执行真实付费/账号远端正向 Probe。