mcpField
P
pylot-cli

pylot-cli

github.com/fellowship-devAgent 工作流3147 次安装2 周前更新
代码仓库

关于 pylot-cli

面向 Pylot CLI 操作的 Agent 技能,指导任务派发、云端认证、任务监控及资产管理。内容还涵盖交互式 devbox 与自主 mission 的选择,以及故障排查边界。

主要功能

  1. 01按任务需求选择 mission 或 devbox
  2. 02通过 device-auth broker 认证云会话
  3. 03派发并监控 mission
  4. 04发现密钥并管理持久化资产
  5. 05区分权限、路由和运行时故障

适用场景

  1. 01在满足任务约定时派发自主开发任务
  2. 02为临时云会话配置 broker 认证
  3. 03跟踪 mission 状态并查看日志
  4. 04保存并发布截图或报告等证据

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/fellowship-dev/dogfooded-skills --skill pylot-cli

安装后在 Claude Code 中输入/pylot-cli确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

evals/
checkpoint-durability-001.json
native-worker-context-001.json
scripts/
file-finding.sh
tests/
file-finding.test.sh

详细介绍

选择执行模式

需要人在回路中的工作应在 devbox 中进行,而不是在 mission 中进行。Mission 是自主运行的操作任务(cron、auto-pylot、技能路由任务)。

Devbox 默认是隔离环境,因此不需要 Git worktree。请在 devbox 的常规仓库检出中工作;不要在其中创建 worktree。选择预期的分支和基础分支,保留现有更改,并在该检出中验证应用和测试。如果切换分支导致应用服务中断,请按照文档说明的 devbox 生命周期进行恢复,而不是创建另一个检出。

  • 当任务约定足够完整,worker 无需实时指导即可执行、审查并报告时,使用自主 mission。派发任务,并让 factory 负责整个实现循环。
  • 当工作确实需要实时决策、反复诊断、跨 worker 交接或用户频繁指导时,使用交互式 devbox。当前会话负责提示词、验证、快照和关闭。
  • 交互式不意味着持续指导。优先使用一个完整的提示词,让选定的技能或 runner 负责其常规阶段;然后等到出现问题、失败或结果后,再补充指导。
  • 不要仅仅因为可以观察进度,就把自主 mission 改成交互式。如果某个周期性 mission 需要指导,请改进本应处理该情况的 factory 技能。

验证新的外部云会话

设备认证 broker 使用持久的 pdab_… 共享密钥,对一次性外部云会话进行身份验证。该密钥通过环境设置绑定为 PYLOT_API_TOKEN。只需在已登录的计算机上运行 pylot auth broker enroll 并完成一次受监督的官方设备授权,即可完成注册。后续使用该绑定的会话会自动使用它;不需要各自运行 pylot auth login,也不需要复制 ~/.pylot/credentials 目录。Broker 会在服务器端保留并刷新授权;若连续 30 天未能成功刷新,则需要重新注册。

这与 PYLOT_BROKER_TOKEN 不同;后者是 mission/worker broker JWT,仅限用于心跳和 Git 令牌签发。PYLOT_API_TOKEN 也可能存放其他类型的令牌:在应用设备认证 broker 规则之前,应检查安全的身份响应,而不是查看变量内容。不要假设它始终是会话 JWT。

操作仓库之前,请明确使用预期的网关和组织:

pylot --version
pylot auth broker --help   # proxy-injected cloud broker workflows require CLI 0.9.20+
pylot --url https://hooks.fellowship.dev --org <org> auth status
pylot --url https://hooks.fellowship.dev --org <org> devboxes projects
pylot --url https://hooks.fellowship.dev --org <org> devboxes project <org/repo>
pylot --url https://hooks.fellowship.dev --org <org> devboxes list <org/repo>

对于此 broker,auth status 会报告 type: device_broker;如果存在环境绑定,_cli.tokenSource: PYLOT_API_TOKEN。请确认已授予预期组织的访问权限,然后分别验证所需的原生读取操作。身份响应可能会列出多个组织,且 org: null;这不能证明后续请求会选择正确的组织,或拥有对应路由的权限。请通过发现流程解析实际的项目标识符,不要猜测仓库名称。

在托管云环境中,请检查受支持凭据的就绪状态,并保留继承的代理、TLS 信任和身份验证保护措施。绑定变量可能包含代理占位符,而非真实密钥;切勿打印该变量、检查凭据文件,或用复制的凭据替换它。不要仅仅因为看不到原始令牌,就启动交互式登录。

该 broker 为组织范围的读取以及 devbox/worker 操作设置了明确的端点允许列表。已授予的作用域并不授权访问所有网关路由。请区分以下故障:

  • broker_org_required:选择目标组织。如果显式指定 --org 后仍然出现此问题,请报告为选择器传输失败;不要重复登录。
  • broker_route_not_allowed:该操作不在代理当前的允许列表中。停止该操作并报告所需权限;不要悄悄切换到个人凭据。
  • broker_reauth_required、broker_grant_revoked 或 broker_secret_revoked:与所有者在已登录的机器上,根据服务端提供的具体恢复提示进行处理。代理管理(status、revoke、rotate-secret)需要个人登录;代理密钥无法管理自身。轮换会立即使旧密钥失效,因此需要更新环境绑定。

**云兼容性:**使用 CLI 0.9.20+ 和包含 pylot#3988 修复的网关。现在组织选择支持代理占位符,代理还支持只读仓库上下文/操作手册,以及组织范围内的 route 和 dispatch(包括 --dry-run)。上下文仅解析密钥名称。常规范围、技能、模型和活动任务检查仍然适用;代理不能强制派发或跳过预检。管理写入、密钥值、Git 令牌创建和代理管理仍不在其允许列表中。较旧的 CLI 即使指定了 --org,仍可能返回 broker_org_required;在诊断新的登录故障前,请先确认已安装的可执行文件和网关版本。预检通过并不表示任务已入队,也不能证明已原生执行;请独立测试所需工作流。

**SSH 不可用:**本地缺少 SSH 密钥、TCP 路由受阻或 SSH 密钥管理被拒绝,并不能证明 devbox 执行不可用。如果代理身份验证以及目标 devbox/worker 读取均成功,请通过 HTTPS 使用 workers prompt,在配置的 devbox 中运行远程 LLM。复用兼容的已记录 worker,保留其组织和所有者范围,并按照 Drive 中的说明验证已确认的回合和原生结果。对于没有任务所有权的外部会话,请省略 --mission;在 worker 命令中保留显式网关 URL 和 --org。

将 broker_route_not_allowed 响应视为对该操作的拒绝:停止被拒绝的操作,然后独立检查已授权的 worker 工作流。不要重复登录、申请 SSH 管理员权限、更改凭据,也不要仅因 SSH 管理被拒绝就报告所有执行均受阻。如果提示操作本身被拒绝,或远程运行时发生故障,请保留该具体故障,并报告所需的权限或运行时恢复措施。

派发任务

CONV_ID=$(ls ~/.claude/session-env/ | head -1)
pylot dispatch "<task>" --agent <team>.<role> --repo <org/repo> --context "conversation_id=$CONV_ID"

自动唤醒时始终传入 --context conversation_id=…。每个派发载荷都必须满足以下要求:

  • /skill 前缀:任务文本必须以显式的 /skill 开头;自由文本会在启动时失败,并返回 routing failed: task has no explicit /skill(退出码 1,消耗 0 个令牌)——但 auto-pylot 和 investigate … report findings 关键词回退路径除外。
  • team.role:团队名称和角色可能变动,因此以 pylot teams list 的结果为准;pylot route 会在派发前验证 <team>.<role>。
  • 提示词长度:约 2560 字节(ECS_OVERRIDES_LIMIT_BYTES 8192 − DISPATCH_OVERRIDES_RESERVED_BYTES 5632)——完整规格请放在议题评论中。
  • 不要包含密钥:任务约定必须自包含,且不得包含任何密钥。

**跨组织:**仓库所有者和所选凭据是两个独立输入。在 CLI 的默认组织之外,请先使用全局选项显式选择凭据:pylot --org <Org> context <Org>/<repo> …。未指定范围的跨组织调用若返回普通的 403 forbidden,并不能证明仓库、操作手册或权限不可用——在诊断授权问题前,请使用仓库所属组织重试。pylot auth status 会显示哪些组织持有凭据。

监控任务

pylot missions list                      # recent missions
pylot missions view <job-id>             # full detail
pylot missions logs <job-id> --follow    # tail CloudWatch logs

终止状态:done、failed、cancelled、timeout。

密钥发现

pylot secrets tree                       # full secrets tree
pylot secrets get <path>                 # bundle keys + fingerprints (no values)

加载到对话中:pylot conversations resources-add <conv-id> type=secret ref=pylot/<path>

资产

你没有 S3,只有资产 API。任何需要持久保存的内容——截图、长篇报告、录音——都必须通过 pylot assets,绝不能直接写入,也不能链接到其他地方。绝不能使用功能分支上的 raw.githubusercontent.com,也绝不能使用静态 S3 键:分支或保留期一结束,这两者都会失效——收据请参阅托管与持久性规则。

三种生命周期——制作之前,先选择与你要制作的内容相符的一种:

生命周期用途方式
临时草稿本轮仍在迭代的草稿无——保留在本轮中
可恢复检查点必须在本轮结束后保留的私有草稿/报告presign --conversation <id> → PUT → finalize(finalize 只接受 --sha256/--size,不接受 scope 标志)
已发布制品面向人员或其他仓库的证据(截图、PR 证明)使用下方完整的 presign → finalize → publish 流程

不要为了走捷径或重试而对检查点使用 publish --conversation <id>——它会设置 visibility: public,这会让原本应为私有草稿的内容出现隐私倒退。所有权已在 presign 时通过 --conversation <id> 确定;检查点不需要单独的“关联到对话”步骤。

检查点的保留期限与 evidence_class 无关——它没有特殊的 TTL 豁免。retention_policy 默认已是永久保留,因此显式传入不会改变任何内容。不过,在暂存环境中,所有资产(不论类别和 retention_policy 值)仍会在 30 天后自动过期——这是 API 之外的 S3 对象生命周期规则,无法针对单个资产覆盖。请将暂存环境中的检查点视为暂存环境下的临时资产:它适合跨越轮次保留内容,但不能替代在报告完成后提升已完成的制品。完整策略请参阅保留期限。

临时草稿并不持久:一轮结束时,它可能会比普通函数返回时丢弃内容来得更突然(这种故障形态的简要说明请参阅 Lambda 冻结约定)。请使用明确的触发条件,而不是等到感觉“快不行了”:如果对话中出现 context capacity 系统消息(根据 issue #944,该消息会在上下文用量约为 90% 时触发),那就是本轮最后的机会——在做其他任何事之前,将现有内容保存为私有对话资产检查点。如果尚未出现该消息,但你即将结束本轮且报告仍未完成,也要创建检查点;警告只是兜底,不是让你等到它出现的许可。

完整的 Pylot 资产生命周期请使用 CLI。CLI 之外唯一允许的操作,是直接向短时有效的预签名对象 URL 执行 PUT;绝不要使用 curl 调用 Pylot 网关资产端点。

FILE=/path/to/evidence.png
CONTENT_TYPE=image/png
SIZE=$(wc -c < "$FILE" | tr -d ' ')
if command -v sha256sum >/dev/null 2>&1; then
  SHA256=$(sha256sum "$FILE" | awk '{print $1}')
else
  SHA256=$(shasum -a 256 "$FILE" | awk '{print $1}')
fi

# Choose the ownership scope required by the destination: --repo, --job, or --conversation.
PRESIGN=$(pylot assets presign \
  --content-type "$CONTENT_TYPE" --size "$SIZE" \
  --repo <org/repo> --filename "$(basename "$FILE")" \
  --evidence-class visual --retention-policy <repo-policy>)  # use the retention policy required by the owning repo (e.g. indefinite, 90d)
ASSET_ID=$(printf '%s' "$PRESIGN" | jq -r '.asset_id')
UPLOAD_URL=$(printf '%s' "$PRESIGN" | jq -r '.upload_url')

# The presigned URL carries its own credentials; do not add a Pylot auth header.
curl -fsS -X PUT "$UPLOAD_URL" -H "Content-Type: $CONTENT_TYPE" --data-binary @"$FILE" && \
  pylot assets finalize "$ASSET_ID" --sha256 "$SHA256" --size "$SIZE"

对于要作为持久审核证据的截图和录音,证据类别应使用 visual。请遵循所属仓库要求的保留和证据策略;除非有意为之,否则不要将临时对话图像归类为永久证据。

完成 finalize 后,请根据目标位置使用相应的动词:

pylot assets view "$ASSET_ID"                              # metadata + temporary GET URL
pylot assets attach "$ASSET_ID" --mission "$PYLOT_JOB_ID" # private mission evidence
pylot assets publish "$ASSET_ID"                          # public capability URL
pylot assets publish "$ASSET_ID" --conversation <id> --alt "description"
pylot assets unpublish "$ASSET_ID"                         # revoke public capability URL

发布会返回 public_url。请先使用常规发布。只有当所属策略明确要求经过审计的覆盖操作,或服务器返回相应的策略限制时,才添加 --override-policy;绝不要预先使用它来绕过策略。对话发布既会发布资产,也会将其关联到对话;attach --mission 会记录私有任务证据,但不会发布资产。六项生命周期操作——presign、finalize、view、attach、publish 和 unpublish——仍然都是网关操作,必须通过 pylot assets 执行。

团队设置

一条命令,一次往返,结果可见——打印从服务器读回的值(PATCH 后)(#3094):

pylot teams config <team> deploy.release_mode=ship   # dotted-path set + read-back
pylot teams config <team> budget_daily_usd=600
pylot teams get <team> --fields deploy,cron          # scoped read of stored config
pylot teams update <team> key=value [...]            # multi-field PATCH (no read-back print)

可变字段(由服务器验证;400 响应会通过 valid_fields 列出当前有效字段): budget_daily_usd enabled org chains provider_chain cron fargate fargate_size deploy operators worker_images repos distill_enabled context_warn_pct context_hard_pct slack_channels。

读回保证:服务器无法持久化的字段会被拒绝并返回 400——绝不会出现接受后丢弃的情况(往返语料测试会强制执行此保证;团队级别的 skills 正是因此从字段集合中移除的——操作员技能位于 operators.<role>.skills 下)。如果写入“成功”但读回为 null,那就是一个 bug——请提交问题;不要尝试使用自创的负载重试。

Runner 合约

名称以 -runner 结尾的技能,必须在 devbox 外部运行,并且需要一个 devbox。这就是完整定义。无论调用者身处何处,都适用:无论是通过 cron 运行的 Pylot 操作员,还是本次会话在这台 Mac 上通过网关启动 devbox。由此可得:

  • Runner 绝不会自行执行引擎。它会启动或指定一个 devbox,发送提示词、轮询、验证并报告。如果你发现自己在内联运行引擎的步骤,那就说明你用错了技能。
  • Runner 依赖于已安装在 devbox 内的引擎技能(例如,improve-code-quality-runner 需要 worker 中有 improve-code-quality 和 test-in-staging)。目前,这项依赖仅以文字形式写在 runner 的 Prerequisites 中,并且没有任何机制负责安装;只有当目标仓库自带该技能时,worker 才会拥有它。在 Pylot frontmatter 合约和 worker 启动时安装功能上线之前(fellowship-dev/pylot#3540),向目标仓库派发 runner 前,请检查其 .claude/skills/。
  • 引擎名称不带后缀,必须能在任何检出目录中使用,且无需访问网关。仅按计划派发 worker 的封装也属于 runner,并且应继续保存在 pylot-skills 中,作为私有内容。
  • pylot skills list --kind runner 是筛选 runner 的合约。

预检与派发

派发前,请确认:

  • 已通过身份验证的 CLI 可以连接 Pylot;
  • 当前团队、角色、技能路由、仓库、worker 镜像和预算均支持所选模式;
  • 相关自动化不会重复或干扰这项工作;
  • 任务合约内容自足、不包含机密,并明确指定目标技能。

通过 CLI 派发,并保留返回的 mission 或 worker 标识符。不要改用原始网关调用或未记录的本地状态。有关证据和资源,请遵循 pylot-cli;不要在此重复其生命周期。 对于规模较大的远程工作,请提前确定分支/基准、Git 身份和证据可见性,并尽早证明一个小型、获授权且可持久保存的检查点。不要在发现发布受阻之前,先累积数小时的工作。 保留先前的限定范围授权;区分实际的工具拒绝与 Agent 的解读,并使用当前证据调查后者。 恢复或压缩上下文后,请依据实际工具回执和已接受的任务,核实声称的阻碍。缺少具名模型工具并不能证明已安装的 CLI 无法使用;请通过可用的 shell 查看其帮助信息。使用具体且有边界的操作、保留的授权和明确的产物目标位置继续工作,不要写冗长复杂的交接叙述。绝不要绕过实际的访问拒绝。

Workers — 启动、驱动、停止

Worker 是运行目标仓库的 Fargate devbox。所有权按范围划分: spawn 和 list 必须且只能指定 --mission / --conversation 之一;每个 worker 的动词使用 --mission,或回退到无作用域的 /workers/:wid 路由。使用 --help 确认标志——不同版本的镜像 CLI 可能有所差异。

1. 预检仓库
pylot devboxes project <org/repo>    # → task_def{family,revision}, required_env_ok

没有 task_def 表示从未构建过镜像,启动时会报 CannotPullContainerError。先构建:pylot deploy build-worker <org/repo> --wait (需要管理员凭据——操作员会收到 403;参见 §6)。启动失败会返回明确类型: 404 no_project/no_repo(仓库不在任何团队的 devbox 配置中)、409 任务 已进入终态、422 provider_required、502 ECS/密钥问题。

2. 选择复用、恢复或启动

对于同一项正在进行的交付任务的下一轮,先检查记录的工作器、范围、任务 ARN 和已完成轮次。通过以下检查后,复用正在运行且健康的 devbox 及其原生上下文;不能 仅因开始新一轮就启动新工作器或新的原生会话。切勿借用其他所有者的工作器,也不要 复用仓库、执行身份不兼容或存在未完成轮次的工作器。

  • 热复用: 如果工作器上一轮已完成、原生身份保持完整,且工作区/应用检查仍通过, 则继续使用记录的工作器。
  • 恢复: 如果该工作器已停止,使用 §4 中经验证的快照和持久恢复流程,然后验证 原生会话连续性和应用就绪状态。
  • 启动: 如果没有兼容且可复用的工作器,或明确的恢复决策要求启动新工作器,则 使用新的工作器。记录复用不合适的原因。

热复用应在授权的工作时段和预算范围内进行。活动工作结束后,优先缩短空闲暴露时间并 经验证后停止;不要添加保活机制、延长 TTL 或让计算资源整夜运行来改善计时结果。分别 记录资源的存续时间、分配的 CPU/内存和实测计费成本;资源分配估算不等于账单。

# inside a mission — your own job
pylot workers spawn --mission "$PYLOT_JOB_ID" repo=<org/repo>
# from a conversation — name= is the human-readable pretty name (tag pylot:name)
pylot workers spawn --conversation "$CONV_ID" repo=<org/repo> name=<short-purpose>
# from a local session (a laptop running `pylot auth login`, no mission context):
pylot devboxes spawn <org/repo> --idle-ttl 3600 name=<short-purpose>   # standalone box, the default
pylot devboxes view <task-arn>                                          # wait for RUNNING before the first prompt
# persistent-conversation variant — capture only the id so the session credential is never printed
CONV_ID=$(pylot conversations create --org <org> --team <team> --repos <repo-name> --title "<task>" | jq -r .id)
pylot devboxes connect <task-arn>      # → ssh_command; drive by SSH when direct command control beats prompting

201 → {worker_id, task_arn, last_status}。启动流程会经历 PROVISIONING → RUNNING (约 1–2 分钟)。提示词会在服务端排队,待容器内守护进程启动后再领取,因此过早发送 的提示词不会丢失;如果需要确认 RUNNING,pylot workers list --mission "$PYLOT_JOB_ID" 会提供实时的 ecs_status(单工作器的 view 不会)。name= 仅在对话路径中有效。

3. 执行
pylot workers prompt <wid> --mission "$PYLOT_JOB_ID" "<text>"     # 202 {turn_seq} | 409 busy|stopped
pylot workers prompt <wid> --mission "$PYLOT_JOB_ID" --wait --timeout 3600 "<text>"
pylot workers view   <wid> --mission "$PYLOT_JOB_ID"              # turn_state, turn_seq, last_result, last_exit_code
pylot workers output <wid> --mission "$PYLOT_JOB_ID"              # result text only
pylot workers logs   <wid> --mission "$PYLOT_JOB_ID"              # CloudWatch tail (--mission required)

当 turn_state 为 idle/error/reaped 且 turn_seq ≥ 提示词返回的序号时, 该轮才算完成。此时才能信任 last_result。--wait 每 5 秒轮询一次,最长 900 秒 (可用 --timeout 覆盖),并在终态不是 idle 时以非零状态退出;--follow 会将 容器日志流式输出到 stderr,且需要同时使用 --wait。如果 devbox 在轮次中途退出, 会以 last_exit_code: -1 标记为 reaped,因此轮询不会无限等待。各阶段之间,应先读取 输出,再发送下一条提示词——不能在失败阶段的基础上继续构建。如果缺少 last_result, 保留原始的 last_output 作为证据;不要捏造成功结果。即使 prompt --wait 返回的 shell 状态为 0,且工作器为 idle,也要检查 last_exit_code 和任何原生会话连续性 失败原因:这些状态可能同时伴随失败的服务提供商轮次。服务提供商以非零状态退出或出现 明确的会话连续性失败,都仍表示阶段失败;应保留其输出并解决问题,再继续发送提示词或 宣称成功。

要继续同一项范围明确的任务,请在上一轮完成后向同一个工作器 ID发送下一条提示词。 保留范围、工作器 ID、session_id、已完成的 turn_seq 和完整输出。通过的轮次只能证明 该轮结果;应用就绪仍需确认预期构建、测试、运行中的服务和功能检查。不能仅凭工作器 处于 idle 状态就报告已就绪。

轮次之间的安全刷新

在更新任何源代码之前,盘点目标仓库和实际检出路径、当前分支/HEAD 与上游,以及暂存、未暂存、未跟踪和关联工作树的状态。例如,在目标检出目录中运行:

git rev-parse --show-toplevel
git branch --show-current
git rev-parse HEAD
git rev-parse --abbrev-ref --symbolic-full-name '@{upstream}' # absent upstream needs an explicit target
git status --short --untracked-files=all
git diff --stat
git diff --cached --stat
git worktree list --porcelain

核实远程仓库的身份,但不要暴露包含凭据的 URL。必要时私下检查相关差异;不要输出机密或无关工作。脏状态是需要保留的工作,并非启动失败。获取已验证的远程仓库(git fetch <remote>),检查传入的提交,但不要更改分支、索引或工作树。不要自动拉取、重置、暂存、清理、切换分支或变基。如果当前源代码正是预期状态,就继续使用。只有在检出目录干净且位于目标分支时,才可快进到明确验证过的目标;执行前须确认当前 HEAD 是其祖先,并审查传入内容的范围(git merge --ff-only <verified-target>)。如果存在分叉、脏状态或目标分支不同,应保留盘点结果,并制定整合方案,不要移动或丢弃他人的工作。

仅包含代码的提交本身不会使健康的环境失效。根据仓库的就绪契约对比发生变更的输入:依赖锁文件、运行时/镜像、启动/服务配置、架构/迁移以及必需的配置变更都可能使先前的验证结果失效。这些输入发生变化时,运行受影响的设置或就绪检查;指纹未知时必须验证。切勿仅为刷新就绪状态而重新运行会产生破坏性的设置或重建数据库。在声称下一轮已就绪之前,验证必需服务的健康状态,并执行有意义的任务操作(适用时运行针对性检查和浏览器/作业流程)。复用缓存和未受影响的就绪验证结果,但仍须完成仓库要求的全部服务门槛,并记录源代码和运行时版本。

调用远程斜杠命令前,先发送纯文本发现提示词,要求列出工作进程已安装的命令和技能,以及仓库说明——本地技能并未安装在远程环境中。即使运行框架报告退出代码为 0,Unknown command 结果仍表示本轮失败。

两种无需考古即可修复的启动故障:

  • 提示词队列一直等待,view 显示 queued → idle、last_exit_code: -1、session_id: null、冻结的 heartbeat_at,且 /ecs/pylot-workers 中没有 [worker-prompt-daemon] 启动行 → 工作进程镜像早于提示词守护进程版本。运行 pylot deploy build-worker <org/repo> 并重新生成工作进程。
  • 本轮以 403 unknown job / unknown devbox worker 失败 → 代理主体回归;检查工作进程行中的 job_id,并向网关提交问题报告。

如果 pylot workers prompt --help 中没有 --wait / --follow / output,说明此容器中的 CLI 版本较旧:使用休眠循环轮询 view,或使用 §7。

在聊天/Lambda 运行时中,没有预算阻塞等待 --wait——切勿忙等轮询。发送提示词后,安排唤醒(参见 Async Wake Pattern),并在下一轮重新检查 view。

4. 停止(有破坏性)并恢复
pylot workers stop   <wid> --mission "$PYLOT_JOB_ID" --force
pylot workers resume <wid> --mission "$PYLOT_JOB_ID" --wait --timeout 900
# Observe the acknowledged attempt without initiating another restore.
pylot workers restore-status <wid> --attempt <restore-attempt-uuid> --wait --timeout 900

必须使用 --force——CLI 不带此参数会拒绝停止。停止有人正在使用的环境前,先征得人工同意。停止操作具有幂等性;技能完成时务必停止任务工作进程(完成时收集结果只是兜底措施,不是计划)。对话所属的开发环境盒会在停止时创建快照;任务工作进程的快照则为可选项,默认关闭(用于控制成本),因此除非确认该标志已开启,否则应将停止任务工作进程视为最终操作。停止前,先收集已完成轮次的完整输出:停止操作可能会将 last_output 替换为截断的快照转录。stopped: true 并不意味着可以恢复——只有 snapshot_status: verified 才能作为依赖 resume 的依据。保留停止回执和快照状态/原因;快照失败或缺失时,必须明确制定恢复决策,不得乐观地声称可以恢复。 要实现持久恢复,需要 CLI 0.9.14 或更高版本,以及支持异步恢复的网关。 开始前,请查看 pylot workers resume --help 中的 --wait 和 pylot workers restore-status --help;CLI 或 worker 镜像版本较旧,并不能证明这些语义可用。

恢复会启动一次持久化尝试。HTTP 202 且 restore_status: pending 表示已确认收到请求,并不代表恢复成功,即使 CLI 以状态码 0 退出也是如此。请立即在任务检查点中保存 worker_id、restore_attempt_id、快照 ID、可用时的目标 task_arn,以及原始范围。--wait 会通过短时间状态读取来观察该次尝试。超时或观察连接断开都不会取消恢复:请使用保存的 worker ID 和尝试 ID 重新连接,并查询 restore-status。等待超时后,不要重复发送 resume POST。如果确认响应丢失,请先检查 restore-status <wid> 和 worker 状态,以恢复尝试标识,再判断是否可以安全地再次发起恢复。运行时恢复期限与清理生命周期独立于 CLI 观察者超时。状态观察可以协调并完成该次尝试、推进清理流程,包括停止失败或已过期的目标;它不会发起新的恢复。请保留 cleanup_pending 原因,并确认清理已进入终态;不要将观察者超时或 StopTask 接受请求视为已确认停止。

只有当状态仍对应保存的 worker 和尝试、restore_status 为 succeeded、恢复回执与该次尝试及快照相符,且目标任务处于 RUNNING 状态时,才可接受恢复完成。请保存新的任务 ARN;切勿继续使用已停止的源 ARN。若状态为 failed 或身份不匹配,请检查记录的原因,并先解决恢复问题,再发送其他提示词或重试。网关确认恢复成功只代表恢复本身成功;工作区、本机上下文和应用就绪状态仍需通过下文的检查来验证。

对于独立 devbox,请使用:

pylot devboxes resume <stopped-task-arn> --wait --timeout 900
# Use the destination task_arn from the completed attempt for subsequent calls.
pylot devboxes view <destination-task-arn>
pylot devboxes connect <destination-task-arn>

devboxes resume --wait 会跟踪已完成尝试的目标 ARN,然后等待其进入 RUNNING 状态且可访问。如果观察者超时,请按上述方法,使用保存的 worker_id 和 restore_attempt_id 查询 workers restore-status,然后检查并连接到该目标。仅 daemon 可访问,并不能证明应用服务已就绪。

本机上下文的连续性因提供商而异。 网关 session_id 用于标识驱动循环,并不能证明这是 Codex 的本机线程。具备 Codex 本机连续性的 worker 镜像必须在 ~/.codex 下持久保存从该网关会话到 thread.started 身份的映射,然后恢复确切的本机线程。如果 rollout 缺失,切勿改用 --last、从其他会话推断 ID,或悄然从头开始。源支持的绑定路径为 $HOME/.codex/pylot-worker-sessions/<sha256-of-gateway-session-id>.json:仅检查其中的网关 ID、thread_id 和 identity 字段,以比较已保存的提供商/凭证路由和路径;切勿输出原始 rollout 历史或凭证值。继续操作前,请验证执行身份相同(提供商、提供商类型、凭证引用、实际工作目录和本机主目录)。支持在同一提供商和凭证路由内切换模型;请记录每轮所用的模型,不要仅因模型变更就判定不兼容。

Claude 使用自己的会话/历史记录及 --resume 语义;不要将 Codex 的身份规则应用于 Claude,也不要假设任一提供商可以恢复另一方的历史记录。

  1. 停止前,保留 worker 和 gateway 会话 ID、provider/native 身份证据、已完成的轮次序列、完整结果,以及任何未完成工作的检查点。记录路径和标识符,但不要倾倒凭据、环境变量或原始 native 历史记录。
  2. 验证快照后,以相同范围恢复同一个 worker ID。观察同一次恢复尝试,直至确认成功;预期会有新的任务 ARN。在发送下一个提示词前,检查已恢复的工作区和 native 会话证据;仅凭 session_id 未发生变化并不足以证明恢复成功。
  3. 向该 worker 发送下一个范围明确的提示词。确认序列已推进,且 provider 已恢复保存的 native 历史记录(例如,它能回忆起前一轮中的某个独特任务事实,而你没有再次提供该信息)。在宣称已就绪前,捕获结果并完成应用的功能检查。

源代码支持某项功能,并不能证明已发布的 worker 镜像包含该功能。宣称 native 连续性前,请先验证镜像/版本和观察到的恢复行为。 对于从未保存 native 映射或 rollout 的旧版 Codex worker,不能根据 gateway 会话 ID 推断已丢失的历史记录。保留文件系统和输出证据,报告缺失的历史记录,并明确决定是迁移到全新会话还是进行恢复。不要将此类迁移称为 native 恢复。 删除对话具有破坏性;除非收到明确要求,否则绝不要这样做。

5. 多箱协作——你就是消息总线

各个箱子之间无法互相通信。预检后,为每个仓库各启动一个箱子,并使用不同的用途名称,然后手动转交信息:workers view A → 提取关键证据(请求 ID、堆栈跟踪、负载形状)→ 将这些信息通过 workers prompt 发给 B。 转交的上下文应简短且基于事实;绝不要在箱子之间传递机密——每个箱子都已通过 env 携带自己的配置包。两个箱子会让预算消耗速度加倍,因此只有在两个仓库都确实需要访问代码或日志时,才启动第二个箱子。

6. 凭据可以调用的内容

能力门控仅对任务容器运行时使用的操作员 JWT 生效 (PYLOT_OPERATOR_TOKEN,别名为 PYLOT_DISPATCH_TOKEN)。操作员拥有所有 操作员能力,因此门控只需判断一个问题:该路由是否有映射?任务限定的工作器路由有映射;未限定范围的路由则没有。

例外 — 会话 JWT($PYLOT_API_TOKEN):以下会话限定路由 已映射到 org:member 层级(route-capability.mts:224-229),并且可以通过会话 JWT 访问 — 能力门控会放行这些路由,每个处理程序会在服务端自行执行授权:

路由起始版本授权边界
POST /conversations/:id/slack-post#2622处理程序:组织成员
POST /conversations/:id/slack-pickup#2622处理程序:组织成员
POST /conversations/:id/admin-action#2891处理程序:通过 turn-trigger 检查组织管理员身份
POST /conversations/:id/admin-action/confirm#2891处理程序:通过 turn-trigger 检查组织管理员身份

这些是唯一接受会话 JWT 的会话限定路由。所有其他会话限定路由和所有 /admin/* 路由仍会对会话 JWT 返回 403 — 这是有意为之。

调用任务操作员Devbox 工作器 · 本地 pylot auth login
workers spawn/list/view/prompt/output/stop/resume/logs 带 --mission是是
相同动词 不带 --mission(未限定范围的 /workers/:wid)403是
workers restore-status(GET 观测,不带 --mission 标志)是,自己的工作器是
workers spawn/list --conversation403是
devboxes projects、devboxes project <org/repo>是是
devboxes spawn/view/connect/delete/list403是
deploy build-worker、deploy promote403是
deploy status <build_id>是是

门控返回的 403 内容为 {"error":"forbidden","reason":"capability_required","capability":"unknown"}。 操作员 JWT 以外的内容都不受能力门控,但处理程序级别的组织限制仍适用于组织范围的令牌。在任务内部,对于接受 --mission "$PYLOT_JOB_ID" 的动词,始终传入此参数。restore-status 则使用已保存的工作器和尝试 ID,通过其获授权的 GET 观测路由进行处理;它可以协调现有尝试并按上述说明执行清理。

7. 没有可用 CLI 时的回退方式

仅适用于镜像中没有 pylot,或其版本过旧而不支持所需动词的容器。PYLOT_JOB_ID、PYLOT_API/PYLOT_GATEWAY_URL 和 PYLOT_DISPATCH_TOKEN 在操作员和任务工作器容器中都会设置。

AUTH=(-H "Authorization: Bearer $PYLOT_DISPATCH_TOKEN" -H "Content-Type: application/json")
BASE="${PYLOT_API:-$PYLOT_GATEWAY_URL}/missions/${PYLOT_JOB_ID}/workers"

WID=$(curl -s --max-time 90 -X POST "${AUTH[@]}" -d "{\"repo\":\"$REPO\"}" "$BASE" \
      | python3 -c 'import sys,json; print(json.load(sys.stdin).get("worker_id",""))')
SEQ=$(curl -s --max-time 30 -X POST "${AUTH[@]}" -d "{\"prompt\":\"$PROMPT\"}" "$BASE/$WID/prompt" \
      | python3 -c 'import sys,json; print(json.load(sys.stdin).get("turn_seq",""))')
# poll GET "$BASE/$WID" until turn_state=idle AND turn_seq=$SEQ, then read last_output
curl -s --max-time 30 -X POST "${AUTH[@]}" "$BASE/$WID/stop" >/dev/null 2>&1 || true

路由和语义与 §2–4 相同。只要有 CLI,就优先使用它 — 采用一种传输方式,确保信息源唯一。

监督运行

启动工作器或提示词后,立即进行一次简短的状态检查,确认任务已被接受并正在启动;排队或正在配置尚不算运行中。 优先使用完成事件,或使用仅在终止状态时唤醒会话的分离式状态监控器。 必须安排定时检查时,使用自适应的 10–30 分钟间隔:临近预期结果或已知故障时约 10 分钟;对于运行正常、预计持续数小时的实现或测试工作,间隔 20–30 分钟。 如果检查结果显示状态正常且没有变化,应延长下一次等待时间,而不是再次发送提示词。 只有在需要具体诊断启动问题或故障时,才缩短检查间隔。 每项任务最多保留一名后续跟进负责人;其他会话接手时,暂停原有跟进;任务完成或出现阻碍安全工作的决策时,也应暂停。 遇到自动化错误或安全拒绝时,应检查并停止反复重试,而不是不断唤醒同一受阻操作,或改换途径规避限制。

只有在证据表明工厂无法安全继续时才介入:任务约定存在重大歧义、执行条件无法恢复,或结果会违反范围或安全要求。 优先从持久化的远程状态恢复。 不要指导常规实现,不要仅因工作不够完善就重写,也不要为反复出现的工厂缺陷构建仅对当前会话有效的变通方案。

验证终止结果

进程成功退出并不等于交付已完成。应依据任务范围独立验证所需结果:

  • 任务终止状态、报告和成本相互吻合;
  • 预期分支或 PR 已存在,且目标和范围正确;
  • PR 中的陈述与实际 diff 和记录的检查结果一致;
  • 审查建议和任何剩余风险均清晰可见;
  • 对于交付任务,审查中发现的问题已解决,并且经授权的发布工作通过目标环境的工作流验收继续推进,同时提供已部署版本和运行时证据;PR 或工作器完成只是中间结果;
  • 附加证据可通过预期的 Pylot 资源流程访问;
  • 执行资源已停止,或以其他方式处于预期的终止状态。

工作器完成后,协调者负责后续剩余的交付工作。 明确仅要求 PR 的任务,在 PR 验证通过后即可结束。 如果验证发现可复用的缺陷,应修正其所属技能,而不是在此处绕过它。

提交发现的问题——绝不直接运行 gh issue create

任何自动化发现的问题(在任何任务、阶段或审查期间注意到的错误、缺口或后续事项)都必须通过此辅助工具提交;每位操作员随附的技能中均包含该工具:

FF="${PYLOT_WORKSPACE:-$HOME/.claude}/skills/pylot-cli/scripts/file-finding.sh"
bash "$FF" --repo <org/repo> --title "<title>" --search "<root-cause terms>" \
  --body-file <evidence.md> [--severity P0|P1|P2|P3] [--incident] [--blocking] [--label <l>]...

该工具强制执行负责人规则(2026-10-06,减少问题流入量):

  1. 如果存在与 --search 匹配的未关闭问题(根因:符号、文件、错误文本或指纹),则添加评论,而不是新建问题。
  2. 如果发现的问题不是 P0/P1、不是事故,也不阻碍 PR 或发布,则将其作为评论添加到仓库唯一的滚动问题 Weekly findings digest — YYYY-Www(标签为 digest)中;同一标题只添加一次。新一周的摘要会关闭上一周的摘要。
  3. 每次提交者运行最多新建 3 个问题,所有仓库合计(FINDING_CYCLE,默认使用任务 ID)。P0 和 --incident 不受此限制;超过上限的问题转入摘要。

该工具会输出 commented N、digest N <reason> 或 created N;请引用其中的数字。 --dry-run 不会写入任何内容。没有该辅助工具时,手动遵循同样的三条规则。 例外:阶段通过固定标签维护的跟踪问题(每个标签一个),以及重新打开已关闭的问题。

通过 CLI 进行 GitHub 身份验证

pylot auth login 会将各组织的凭据存储在 ~/.pylot/credentials 中; 当所选主体允许访问该端点时,pylot auth git-token --repo <org>/<repo> 会生成一个一次性、短时有效的 App 安装令牌。设备身份验证代理登录用于 Pylot 服务身份验证;其当前允许列表不包含 Git 令牌生成。在由连接器支持的 Buddy 云端设置中,请针对预期仓库,在每次调用时使用 PYLOT_GH_AUTH_MODE=inherited gh ...。 请将连接器就绪状态和 Pylot 令牌来源视为独立的验收关卡。 App 拥有组织级访问权限,但每个生成的令牌都仅限于你指定的单个仓库——该令牌下 gh repo list 的结果较少并不表示 App 受到限制;如需访问其他仓库,请另行生成令牌。仓库“缺少 pylot 支持”表示其 devbox 配置缺失(不在任何团队中、没有 worker 镜像),绝不表示 App 无法访问它。切勿导出会话级 GH_TOKEN。 App 令牌无法读取用户专属内容(GitHub 通知);这类内容需要已登录的 gh 身份或事件账本路由。密钥:切勿放入提示词或负载中——使用 pylot secrets,然后引用环境变量名称。

Git 需要凭据助手;gh 需要自己的逐次调用令牌垫片。 使用指定仓库的命令验证两者。在托管云环境中,请检查出站代理是否会替换调用方提供的 GitHub 身份验证信息。 如果使用故意无效的令牌也能读取私有仓库,这并不能证明 Pylot 令牌可用。请通过受支持的环境配置解决凭据路由问题,保留代理和 TLS 设置,并在接受身份验证前重新执行实际仓库读取和推送。仅安装 App 或生成令牌不足以作为证据。

在对话中设置组织(admin-action)

需要 pylot#2978 — 在引导 worker 使用此路径之前,worker 镜像中必须包含 pylot convo admin-action。验证方式:在容器中运行 pylot convo admin-action --help,且必须成功。

会话 JWT 携带的作用域为 [dispatch, missions:read, heartbeat]——/admin/* 路由按设计始终返回 403。/conversations/:id/admin-action 是单独的授权边界(参见 §6 异常表),接受会话 JWT;该端点会在服务端根据触发当前轮次的消息发送者强制执行组织管理员授权。

首选路径
# auto-detects $CONVERSATION_ID from environment
pylot convo admin-action <op> [key=value ...]

# explicit conversation id override
pylot convo admin-action <op> --conversation <id> [key=value ...]

# confirm a staged (confirm-tier) operation after org admin replies
pylot convo admin-confirm <code>

# reject a pending confirm-tier operation
pylot convo admin-confirm <code> --reject
操作

操作列表是封闭的——不在此表中的任何值都会返回 400。 切勿在操作参数中传递凭据或 API 密钥——网关会返回 400,且不会存储任何内容(硬性边界:明确拒绝看起来像凭据的参数)。

操作层级效果
setup-status读取(任何对话成员)引导状态:App 安装、团队、仓库绑定及 worker 镜像是否存在、目标、操作员/技能、提供商链、预算
team-create立即执行(组织管理员请求者)在此组织中创建团队
team-rename立即执行(组织管理员请求者)重命名团队
repos-add立即执行(组织管理员请求者)将仓库绑定到团队;响应包含 worker 镜像是否存在
goals-set立即执行(组织管理员请求者)设置团队目标(store auto-pylot 的阶段 00 会读取这些目标)
operator-add立即执行(组织管理员请求者)在团队中创建操作员
skill-assign立即执行(组织管理员请求者)为操作员分配技能
instructions-set立即执行(组织管理员请求者)设置操作员指令
org-update确认组织设置(Slack 默认团队、max_concurrent,…)
budget-set确认(支出)团队每日预算上限
provider-assign确认(模型/支出路由)将现有的平台提供商链附加到组织/团队
确认级操作流程

确认级操作(org-update、budget-set、provider-assign)会先暂存, 然后由组织管理员回复批准——审批者的身份由其自己的对话轮次证明,而不是由工作者提供。

  1. pylot convo admin-action <confirm-op> [args] → 网关返回 202 {code, summary}
  2. 工作者转告用户:“组织管理员必须在 10 分钟内回复 confirm <CODE>。” (summary 会准确描述将发生的更改——请原样转达。)
  3. 组织管理员发送回复;审批轮次中的消息发送者本身即可证明其身份。
  4. pylot convo admin-confirm <CODE>(在审批轮次中调用)→ 200,操作已执行。
  5. 拒绝或过期:pylot convo admin-confirm <CODE> --reject 会终止待处理操作; 过期代码(超过 10 分钟)会返回 410,且绝不会执行。
错误
错误含义面向用户的说明
no_human_trigger轮次没有人工发送者(定时唤醒、自动化)请从由人工消息触发的对话轮次重试
unlinked_slack发送者的 Slack 账号未关联 GitHub 账号请让用户在 Slack 应用设置中关联其 GitHub 账号
not_org_admin已关联的 GitHub 账号不是此组织的管理员立即执行的操作需要线程中有组织管理员;确认级操作:任何人都可以暂存,但必须由组织管理员确认
terminal_only操作需要凭据或机密值请使用终端 CLI——凭据不能通过对话传递
code_expired确认代码已超过 10 分钟重新运行原始操作以获取新代码
过渡窗口期的 curl 回退方案(仅适用于早于 #2978 的镜像)

仅当容器镜像早于 pylot convo admin-action 时才使用此方案。 $CONVERSATION_ID 在 chat-worker 容器中已设置。请使用 $PYLOT_API_TOKEN (会话 JWT)——不要使用 $PYLOT_DISPATCH_TOKEN(操作员令牌会导致此端点返回 403, 这是设计如此;在此处使用它正是 pylot#2979 要纠正的错误行为)。

# immediate op
curl -s -X POST \
  -H "Authorization: Bearer $PYLOT_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\"op\":\"$OP\"}" \
  "${PYLOT_API:-$PYLOT_GATEWAY_URL}/conversations/$CONVERSATION_ID/admin-action"

# confirm a staged op (after org admin replies with the code)
curl -s -X POST \
  -H "Authorization: Bearer $PYLOT_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\"code\":\"$CODE\"}" \
  "${PYLOT_API:-$PYLOT_GATEWAY_URL}/conversations/$CONVERSATION_ID/admin-action/confirm"

自动化

pylot automations list           # inventory with run stats
pylot automations get <name>     # single rule detail

按仓库查看覆盖情况:从列表输出中过滤 only_repos/skip_repos。

由自动化分发的组织专属管理员操作手册位于该组织的仓库文档和团队手册 (pylot context <org/repo>)中,而不在此技能中。

指标与注释

需要 CLI 0.9.12 或更高版本。

pylot measures list                                  # stored measures with latest version and grains
pylot measures points <measure> --grain day|week|month [--since] [--until] [--measure-version] [--scope <scope_ref>]
                                                     # read one measure series
pylot measures put --file points.json                # upsert a batch of points ({"points": [...]})
pylot annotations add --kind deploy|pause|incident|change|skill_update --start <iso> [--end <iso>] --label <text> \
  [--affects a,b] [--scope <scope_ref>] [--detail <text>] [--source-ref <ref>]
                                                     # record a dated event; no --end = an instant, --end = a period
pylot annotations list [--since] [--until] [--affects <measure>] [--include-superseded]
                                                     # annotations overlapping a range
pylot annotations supersede <id> --by <id>           # replace a wrong annotation (the log is append-only)

有关如何定义指标、添加注释、绘制图表和评估 Objectives,请使用 pylot-objectives 技能。

Slack 频道路由

将 Slack 频道绑定到团队以进行消息路由。多个频道可以绑定到同一个团队(多对一)。CLI 是主要接口——没有对应的 UI。

# Bind a channel to a team (additive; channel must already be known to the bot)
pylot teams channels bind <channel-id> --team <team> [--org <org>]

# List all Slack channels with their bound team (null = unbound)
pylot teams channels list [--org <org>]

# Set the org-level fallback team for unbound channels
pylot orgs set-slack-default <team> --org <org>

# Clear the org-level fallback team
pylot orgs clear-slack-default --org <org>

默认团队回退:Slack 消息到达未显式绑定的频道时,会路由到组织级默认团队(如果已设置)。未绑定且没有组织默认团队的频道会被丢弃。

异步唤醒模式

使用 --context conversation_id=… 分发 → 在任务结束和 PR 生命周期事件发生时自动唤醒。 自行唤醒回退方案:pylot conversations wakes-add <conv-id> in_seconds=300 content="check X" 一次只能设置一个唤醒;请重新安排,而不要堆叠。

评论

支持 Markdown