Files
codex-app-server/docs/thread-lifecycle-align.md
T
host cf08c4cf37 feat: 线程生命周期对齐 15 项(bindings/refs/快照/心跳绑定/移交落点)
对照 Codex app 第二批:GET /tasks/{id}/bindings(项目/
环境/主机/worktree/PR/附件/血缘总表)、/refs 外部引用
增删查、/worktree/archive 快照(branch/head/status/diff/
untracked 路径)与 restore(目录在才解归档、目录删了
409 附快照)、自动化 bind + heartbeat(走 thread/message
同款投递路,cron 绑定不唤醒)、移交 completed 后任务
worktree_host 跟随切换、pr/checks 响应补 head_sha、
thread/read 的 tool 条目补 server/status。

诚实边界见 docs/thread-lifecycle-align.md:已有 worktree
附加、自动化变化静默、建议器、服务端定时触发器未实现;
Memory 对位是待合的 PR #102,本批不重复实现。

三套件:cloud 948 OK(4 跳过)、host 91 OK、deploy 29 OK
2026-10-28 04:56:22 +08:00

12 KiB
Raw Blame History

与 Codex app 的功能对齐:15 项逐项对照

本批(feat-thread-lifecycle)之前已有 #103 的 8 项 (wait/read/message/handoff/worktree/pr-checks/automation/ sleep,见 docs/thread-wait-ops.md)。本文件逐项说明 15 项 在控制面的现状:已实现 / 本批补齐 / 待合 PR / 未实现。 “已实现”指端点真实存在且有单测,不是文档宣称。

1. Agent 线程管理

能力 落点 状态
创建独立线程 POST /tasks 已实现
从当前线程分叉,保留上下文 POST /tasks/{id}/fork(终态任务;worker 调 host.py fork,分叉 thread 历史 + 拷 worktree 文件) 已实现
继续执行暂停/空闲线程 POST /tasks/{id}/continue(终态复活);POST /tasks/{id}/thread/message(终态 CAS 复活、进行中写后续) 已实现
向其他线程发后续指令 POST /tasks/{id}/thread/message;跨任务 POST /tasks/{id}/messages(peer,跨项目拒绝) 已实现
读取状态、最近结果、历史摘要 GET /tasks/{id}/thread/read;GET /tasks/{id}/thread?include_turns=1 已实现(#103)
设置线程标题 POST /tasks/{id}/thread/rename {name}(终态任务 + 本机线程) 已实现
归档和恢复线程 POST /tasks/{id}/thread/archive、/thread/unarchive(终态任务 + 本机线程,经 host.py CLI) 已实现
查看主机、项目、工作目录 GET /tasks/{id}/bindings(本批) 本批补齐

注意:rename/archive/unarchive 走 _task_thread_mutate, 只允许终态任务且本机线程;docker 任务、进行中任务会 409/守卫拦截——这是线程正在变、快照不可信的保护, 不是缺口。归档只归档线程本身,worktree 快照见第 5 项。

2. 并行任务等待

wait_threads = POST /tasks/{id}/wait(#103):最多 8 个、 首个重要变化返回(完成/失败/需输入/需审批/需处理)、 超时回进度、afterCursor 只取新变化。事件等待机制, 不是固定间隔定时器;变化检测只读数据库(任务行、 审批表、后续表),不轮询 trace。

3. 线程状态读取

read_thread = GET /tasks/{id}/thread/read(#103):状态映射、 最近活动、改动文件、命令与退出码、待审批/待后续、 attention 汇总。本批补了一个小缺口:tool 条目原来只取 tool 名,丢了 server 与 status,现在拼成 server/tool: status 一行。test_results 仍为 null: trace 里没有独立测试结果记录,不编“测试通过”。

4. 任务移交和远程主机切换

  • 发起移交:POST /tasks/{id}/handoff {target_host}(#103)
  • 查询进度/是否成功:GET /tasks/{id}/handoff(含 arrived)
  • 对方主机上报:POST /tasks/{id}/handoff/{hid}/report
  • 移交后继续发指令:POST /tasks/{id}/thread/message(与 主机无关,认领方按新主机判断)
  • 本批补齐的落点:report 到 completed 时,任务的 worktree_host 改成目标主机——之前上报只改移交行, 任务行还指着旧主机,后续 /worktree、/pr/checks 会 按旧主机判断,这是真缺口,现已补上。
  • 未实现:服务端不执行 SSH 迁移、不搬文件;迁移本身 由目标主机 worker 执行并回报(见 thread-wait-ops.md)。

5. Git Worktree 生命周期管理

能力 现状
创建独立 worktree 已实现:worker 认领后同步建(host.py make_worktree,一任务一分支 host/,非 git 仓库退化为目录拷贝)
从指定 commit/分支/ref 创建 部分:任务绑 environment 时按 environment.branch 检出(environment 本身是异步创建过程,creating→ready/failed);任务直接建时一律从 HEAD 建,不接受 commit/ref 参数
将已有 worktree 附加到任务 未实现:worker 派发路径按任务 id 确定 worktree 目录,附加需要改派发,不在本批编一个只记关联、不被执行的“附加”
查看创建进度 GET /tasks/{id}/worktree(派生状态;创建是同步的,没有阶段流,不编百分比)
恢复已归档 worktree 本批:POST /tasks/{id}/worktree/restore(见下)
归档不再需要的 worktree 本批:POST /tasks/{id}/worktree/archive 拍快照(见下);真删目录仍是 merge/discard 的职责
保存未提交修改、未推送 commit、非忽略文件 本批快照记:branch、head_sha(即未推送 commit 的位置)、git status、git diff、untracked 路径清单。诚实边界:untracked 文件只记路径不拷内容,diff 只覆盖已跟踪文件;快照是证据,不是可一键还原的备份
保留 worktree 与线程的关联 已实现:快照行含 task_id + worktree_path;bindings 里 worktree 与任务同表返回

Restore 语义(不编造):目录还在时只把快照标 restored (目录没动过,谈不上还原);目录已删时 409 并附快照 (重建需要仓库路径,服务端没有)。

6. 任务与 Git 状态绑定

本批 GET /tasks/{id}/bindings 一次返回:项目(名、 repo_url)、环境(名、分支、执行器、状态)、主机 (worktree_host、local/remote、本机名)、worktree (状态、路径、分支)、分支、PR(url/number)、执行器 (任务值或全局缺省)、附件(文件 + 外部引用)、 worktree 快照清单、血缘(parent_task_id、forked_from、 coordinator_part)。之前这些字段散在任务行、项目行、 环境行、worktree 端点里,没有一张总表。

7. Pull Request 和 CI 诊断

read_pull_request_check_diagnostics = GET /tasks/{id}/pr/checks(#103):checks JSON(workflow、 link、description)、失败 run 的 check-runs 日志原文 (截 4000 字)与行号注释(上限 20 条)。本批只补一个 字段:响应里加 head_sha(PR 当前 commit,之前内部 算出来只用于查 check-runs,没返回)。只读:不重跑、 不批准、不合并。外部 CI 状态即 check-runs 的 conclusion;构建/测试失败日志即 output.text。

8. 自动化、心跳和定时任务

能力 现状
创建/查看/修改/删除长期自动化 POST/GET/PATCH /projects/{id}/automations、cancel(#103 前已有 + #103 的 update)
按时间周期运行 已实现:interval_seconds(60 秒–30 天),到期判断 last_run + interval
定期唤醒已有线程(heartbeat) 本批:POST .../automations/{aid}/bind {task_id, mode: heartbeat} + POST .../automations/{aid}/heartbeat {message?}(缺省用自动化 prompt);投递走 thread/message 同款路(终态复活、进行中写后续、配额门同款)
cron(到期独立跑新任务) 绑定 mode=cron 只作标记;heartbeat 口对 cron 绑定回 409(cron 不唤醒线程)
复用已有对话上下文 heartbeat 唤醒的是同一线程,上下文自然复用;cron 新任务不继承历史(与 fork 的语义一致)
继续执行多日/多周任务 机制上可以(interval 上限 30 天 + 绑定线程长期存在),未做跨周的专项验证
只在变化/完成/失败/需处理时通知,状态不变保持安静 未实现:没有“与上次产出比对、无变化静默”的抑制逻辑;每次运行终态仍按现有推送通道通知
到期触发器 未实现(服务端本身不起定时器):触发需外部调 /heartbeat,或等 PR #101 的 run_due_automation 落地;本批只给了绑定与唤醒口,没宣称“已自动跑”

9. 线程归档和恢复

线程归档/恢复见第 1 项(host.py CLI 透传)。归档时 保留的东西分两处:对话记录与线程状态在 codex-home 的线程存档里;worktree 快照在本批的 task_worktree_snapshots 里;PR 附件在任务行的 pr_url/pr_number 与 bindings 里;未提交文件快照即 第 5 项的快照(status/diff/untracked 路径)。长期 项目、阶段性任务、暂时暂停的 Agent 走“归档线程 + 拍 worktree 快照”两步,不是同一个端点。

10. 任务附件和交付物管理

  • 文件:已实现——task_attachments(上传包绑定,任务行 attachments 字段,fork 时带走清单)。
  • PR、worktree:已实现——任务行 pr_url/pr_number、 worktree 路径,bindings 同表返回。
  • 生成的文档、构建结果、外部服务结果引用:本批—— POST/GET/DELETE /tasks/{id}/refs(kind: doc/build/ external/pr/file,只记引用不存内容、不校验链接可达)。
  • 移除:refs 可 DELETE;文件附件的移除走上传包的 used 状态与任务删除,不在本批另开删除口。

11. 项目和主机上下文

GET /tasks/{id}/bindings 即上下文总表(项目、仓库、 主机、本地/远程、工作目录、默认执行器、环境、归属)。 项目默认执行器:任务 executor 为空时取全局缺省 (_default_executor),没有“项目级默认执行器”字段—— 环境行(executor)是更细的默认,任务绑 environment 时以环境为准。

12. 插件、MCP 和外部连接器

已实现(经 host.py CLI 透传,项目 scope):

  • GET /projects/{id}/skills(skills/list)
  • GET /projects/{id}/apps(app/list)
  • GET /projects/{id}/mcp-servers(mcpServerStatus/list)
  • POST /projects/{id}/mcp-servers/reload
  • POST /projects/{id}/mcp-servers/{name}/oauth-login

限制与官方一致:连接受账户、工作区、管理员权限、 插件安装状态限制;oauth-login 只是发起流程,真正 授权在宿主侧完成;容器内视图与宿主侧视图可能不同 (skills/MCP 端点 docstring 已注明)。GitHub、Slack 等具体外部系统在本系统是另外的连接器(GitHub App、 Slack webhook),不是 MCP 一个口。

13. Memory 和长期上下文

分两层,不要混淆:

  • 已实现(本系统自有):KB(/projects/{id}/kb)与 任务 summaries——按任务/项目存的背景,不是跨任务 的用户长期记忆。
  • 待合 PR:PR #102(feat-cloud-memory-identity, user_profiles + identity_memories,用户级仅本人、 项目级项目成员可见,上下文各取最新 5 条)—— 这才是官方 Memory 的对位。本批不重复实现,避免 与 #102 合并冲突;自动注入任务/thread 是 #102 之后仍待显式决策的下一步(注入点、去重、成本上限)。
  • 可用性限制与官方一致:记忆的范围受账户与发布状态 影响,本系统里受项目成员权限限制。

14. 主动续作和建议性任务

未实现为独立服务。现有拼图:自动化(到期唤醒/独立 跑)+ triage(/projects/{id}/triage,自动化到期与 待处理汇总)+ #102 的记忆 + bindings 的 PR/任务状态。 “根据未完成任务、关联 PR、插件内容、Memory、项目 上下文建议下一步”需要一个建议器(读上述输入、产出 建议任务),不在本批范围;本批的 heartbeat 与 bindings 是给建议器准备的输入口与执行口,不是建议器 本身。不要把 triage 的到期计数说成“已会建议”。

15. 使用量和连接状态管理

已实现(不新开端点,本批只做对照):

  • 账户使用量:GET /users/me/quota、GET /tasks/{id}/cost
  • 限制窗口剩余:quota 的美元配额与无价 token cap
  • 连接的主机:GET /workers(心跳与主机名)
  • 远程主机是否可用:worktree_host + /workers 心跳龄 (_workers_heartbeat)+ /health(db_ok、worker_ok)
  • App 更新状态:/health 的 commit 字段(部署提交)
  • 已安装插件和连接状态:第 12 项的 skills/mcp-servers
  • 项目和线程的运行环境:第 6 项的 bindings

这些用于任务调度与故障判断,不直接跑任务。

本批新增端点一览

  • GET /tasks/{id}/bindings
  • POST /tasks/{id}/refs、GET /tasks/{id}/refs、 DELETE /tasks/{id}/refs/{ref_id}
  • POST /tasks/{id}/worktree/archive、 GET /tasks/{id}/worktree/snapshots、 POST /tasks/{id}/worktree/restore
  • GET/POST/DELETE /projects/{id}/automations/{aid}/bind
  • POST /projects/{id}/automations/{aid}/heartbeat
  • GET /projects/{id}/automations/{aid} 响应加 heartbeat_binding 字段
  • GET /tasks/{id}/pr/checks 响应加 head_sha 字段
  • 移交 completed 上报后任务 worktree_host 跟随切换