29 KiB
Agent Session Fork Design
Status: superseded by the 2026-07-29 optimistic through-turn Fork contract
This document is retained as historical design context and is no longer the
implementation contract. The current contract lives in
docs/architecture/agent-gui-node.md and packages/agent/host/README.md: only
the selected canonical Turn's provider binding is verified; historical prefix
state is trusted; the source remains writable; provider Fork precedes local
materialization; unknown delivery is never redispatched; and no legacy
compatibility path is exposed.
Date: 2026-07-27
Supersedes:
1. 结论
AgentGUI Fork 是 Agent Host 拥有的一等 Session 生命周期能力,不是 GUI 复制消息,也不是 tuttid service 拼装 create + replay。
当前版本完整实现 throughTurn:
- 用户选择一个已 settled 的 canonical Turn;
- 新会话包含该 Turn 及其之前的已稳定历史;
- provider 使用原生 Fork 建立独立推理上下文;
- Store 从 prepare 时冻结的 snapshot 原子创建独立 canonical root Session;
- provider 不支持时,确切 Session 的 Turn 级 Fork 能力为 false,GUI 不展示按钮。
统一基建已经保留两个结构能力:
fork // 完整会话 Fork
forkThroughTurn // 包含指定 Turn 的 Fork
本期只有 forkThroughTurn 具备端到端实现。fork 字段和统一 ForkPoint 已进入各层契约,
但完整会话入口、Host 分支和 provider 接入留到后续,不能仅把 capability 改为 true。
1.1 整体工作流程
1.2 模块架构
2. 架构所有权
AgentGUI Turn action
-> AgentSessionEngine mutation
-> AgentActivityAdapter
-> generated tuttid client
-> tuttid API/service adapter
-> Agent Host durable Fork saga
-> store-sqlite snapshot/operation/commit
-> provider-neutral runtime Fork
- Codex app-server thread/fork(lastTurnId)
- Tutti Agent app-server thread/fork(lastTurnId)
- Claude Agent SDK forkSession(upToMessageId, title)
各层职责:
| 层 | 职责 |
|---|---|
| AgentGUI | 展示能力、提交 canonical boundary、展示 pending/lineage、成功后导航 |
| AgentSessionEngine | 唯一前端 mutation 状态源,生成并复用 request/target identity |
| Activity adapter | 映射 typed operation,并通过 GET 调和 accepted operation |
| tuttid API/service | 鉴权、HTTP 状态、DTO 映射;不拥有 Fork saga |
| Agent Host | 校验、序列化、runtime preparation、dispatch、恢复和提交 |
| store-sqlite | source fence、snapshot、operation、ID remap、canonical atomic commit |
| daemon runtime | 按确切 provider runtime 解析能力并调用 provider Driver |
| Codex Driver | thread/fork、版本门槛、完整 provider Turn prefix 验证 |
| Tutti Agent Driver | 复用 app-server Fork 协议,使用独立版本门槛和 TUTTI_AGENT_HOME |
| Claude SDK Driver | stateless inspect/fork、消息 checkpoint、child UUID 映射与复验 |
这符合 AgentGUI 的既有约束:canonical lifecycle 和 mutation 状态属于 Activity Engine; React 不保存第二份 Fork request、pending 或结果状态。
3. 统一领域模型
3.1 Fork Point
Host 使用显式 Point,而不是为每种 Fork 建独立命令:
type SessionForkPoint struct {
Kind SessionForkPointKind
TurnID string
}
const SessionForkPointThroughTurn = "through_turn"
Store operation 持久化 point_kind 和 source_turn_id。v1 operation 的迁移语义明确为
through_turn,不会在恢复时根据空字段猜模式。
HTTP 使用同形 discriminated union:
{
"point": {
"type": "throughTurn",
"turnId": "canonical-turn-id"
}
}
3.2 两类能力
Session lifecycle capability:
interface AgentActivitySessionLifecycleCapabilities {
fork: boolean;
forkThroughTurn: boolean;
}
能力由确切 runtime Session + Driver attestation 得出,不按 provider 名称硬编码。Host 执行时再次解析 capability,因此 GUI 中缓存的 true 不能绕过服务端校验。
当前规则:
- Codex app-server 版本满足门槛且协议支持
lastTurnId:forkThroughTurn=true; - Tutti Agent app-server 版本不低于
0.0.10且协议支持lastTurnId:forkThroughTurn=true; - Claude Agent SDK 能读取 exact transcript、且 canonical provider Turn UUID
与其 root user-message prefix 匹配:
forkThroughTurn=true; - 其他 Agent、不满足门槛的 Codex/Tutti Agent runtime、以及没有真实 SDK UUID 的旧 Claude
Turn:
forkThroughTurn=false; - 当前所有接入:
fork=false,完整会话按钮不展示。
provider Turn 绑定不能仅凭 ID 非空成立。Turn 只保存通用
RootProviderTurnID 和 opaque provider_turn_binding_json;JSON 的 schema、写入和
forkability 判断均由具体 Agent 的 ProviderTurnBindingAdapter 维护。服务端展示按钮和
实际 Fork 都调用同一个 CanForkProviderTurn hook,Host/Store 不解析 Claude checkpoint
或其他 Agent 私有字段。迁移只把确有旧 checkpoint 证据的行转换为 Agent JSON;仅有
synthetic/provider-looking ID 的历史行仍为 {},投影为 recovery_required,GUI
不展示 Fork。转换完成后旧 Claude 专属列会被删除。
结构 capability 与瞬时 availability 分开:
- capability=false:隐藏入口;
- capability=true 但 Session 忙、存在 pending Interaction 或 boundary 不可证明: capability 仍为 true,但按钮禁用或 Host fail closed;
- Host 永远做最终校验。
4. throughTurn 语义
throughTurn(turnId) 是包含边界:
Turn 1 -> Turn 2 -> Turn 3
^
throughTurn
child = Turn 1 + Turn 2 + Turn 3
它不同于 beforeTurn:后者排除所指 Turn。当前产品和协议只使用 throughTurn,不使用
实验性/deprecated rollback 来模拟。
source 必须满足:
- 是 canonical
kind=rootSession; - provider Session identity 非空;
- 没有 active Turn;
- 没有 pending Interaction;
- 没有未结束 Goal control、runtime operation、submit claim 或另一个 Fork;
- 选中 Turn 属于 source、phase=settled;
- Turn sequence provenance 可验证;
- boundary 中不存在 provider-native descendant lane;
- boundary 中不存在尚未定义 immutable manifest 的 session-local attachment reference。
最后两项当前 fail closed,避免 provider history 与 canonical history 不一致。
5. Child Session
Fork 结果是独立 root Session:
- 新
agentSessionId; - provider 返回的新
providerSessionId; kind=root;rootAgentSessionId、parentAgentSessionId、parentTurnId、parentToolCallId均为空;forkedFrom保存直接 source lineage;- 可以继续对 child 执行下一次 Fork。
child 继承 prepare 时冻结的:
- agent target、provider、model、settings;
- cwd 和 runtime context;
- rail placement、visibility;
- 从 source title 派生并冻结的 child title,格式为
<source title> (<同一 source 的有效 Fork 序号>),首个 child 例如123 (2); - boundary 内的 Turns、Messages、Interactions;非 terminal boundary 在 child
中规范化为
settled/interrupted,pending Interaction 规范化为superseded; - provider 可恢复上下文。
历史 Session 先经过一次 RuntimePreparation,同一份 prepared runtime identity 同时用于
driver attestation、provider dispatch 和 canonical child 的 cwd/settings/runtime context。
runtime context 中引用 provider Session identity 的恢复游标不能越过 Fork 边界直接复用。
Claude adapter 只接受 resumeCursor.resume == canonical providerSessionId 的游标;child
继承到 source 游标时丢弃该游标,并以 forkSession 返回的 child identity 恢复,防止首次
续聊重新进入 source namespace。
env 与 provider target reference 只用于本次 provider dispatch,不作为凭据或临时运行事实写入
canonical snapshot;后续恢复 child 时仍通过正常 RuntimePreparation 从 agent target 和已冻结的
canonical settings/context 重新解析。由于进程重启会把未 dispatch 的 prepared operation 标记为
failed,所以 Host 不会用缺失的临时 runtime 事实跨进程重发 provider RPC。
child 不继承:
- active Turn、pending Interaction;
- Goal state/control;
- runtime operation、submit claim;
- Tutti mode activation;
- prompt queue、unread、pin;
- usage observation;
- provider-native child Sessions。
历史克隆不是原 ID 的浅复制。Store 根据 operation/workspace/source/target/entity/source ID 确定性生成新的 canonical Turn、Message 和 Interaction ID,并统一替换:
- Turn/Message/Interaction owner Session;
- Message/Interaction 的 Turn reference;
FinalAssistantMessageID;- closed allowlist 中的 typed payload references。
普通文本、Markdown、tool output 和 provider/source lineage 不做全局字符串替换。未知 typed reference、重复 key 或非单射 mapping fail closed。同 operation replay 得到相同 ID。
6. Durable operation
6.1 内部状态
Store 当前状态机:
prepared
-> dispatching
-> provider_accepted
-> committed
prepared|dispatching -> failed
dispatching -> unknown
关键约束:
(workspace_id, request_id)幂等;- target Session ID 在 prepare 时独立 reservation;
- 相同 request hash replay 返回原 operation;
- 相同 request ID、不同 hash 返回 conflict;
- provider 已可能接受时绝不自动再次 dispatch;
- provider child 状态必须通过
host_copy或provider_owned的 typed binding evidence 证明独立可恢复,随后才能写入provider_accepted并提交 canonical child; provider_accepted之后只重试本地 canonical commit。- source + point 的 durable boundary barrier 覆盖 active、unknown 和
committed + client-unobserved;新 identity 命中 barrier 时返回原 operation; committed只有经过显式 client-observed ACK 才释放 barrier;ACK 后同一 Turn 仍可创建新的显式分支;unknown不接受 ACK,也不释放 barrier。
operation 持久化:
- operation/request/hash;
- source/target canonical identity;
- source/target provider identity;
- ordered target provider Turn IDs、binding mode 和 verification receipt;
point_kind、source canonical/provider Turn;- commit 后的 target canonical boundary Turn;
- Driver kind/version;
- immutable snapshot/hash;
- status/error 和 dispatch/accept/complete 时间。
lineage 独立存储:
source_agent_session_id
source_turn_id
target_agent_session_id
target_turn_id
operation_id
forked_at_unix_ms
Fork clone 会重映射 canonical Turn ID,因此 source_turn_id 不能用于 child 时间线定位。
commit 从已验证的 identity map 取得 target_turn_id,在同一事务写入 operation 和
lineage。operation 是 hard purge 后 immutable replay 的 authority,lineage 是存活 child
的快速 Session projection。V4 migration 用固定的 canonical-ID v1 规则精确回填历史
committed operation 和 lineage。删除 source 不会把 child 解释成 provider-native child。
6.2 Source fence
prepare transaction 在验证 snapshot 的同时建立 active Fork operation。所有会改变 source Fork 语义的 Store 写路径都检查该 operation:
- Turn/Message/Interaction mutation;
- Session settings;
- Goal control;
- runtime operation;
- submit claim;
- delete/clear;
- 另一个 Fork。
source title 和 pin 不影响已经冻结的 Fork snapshot,允许继续更新。operation 进入 failed、 unknown 或 committed 后 source fence 释放。
Host 的 session mutation actor 提供进程内串行化;Store fence 才是 crash-safe 正确性边界。
6.3 Runtime preparation
live Session 可以复用已附着 runtime。historical/non-live Session 必须走与 resume 相同的
RuntimePreparation:
- cwd;
- env;
- provider target ref;
- runtime context;
- settings。
这些事实只解析一次并冻结;第一次 capability resolve、dispatch 前 re-attestation 和 实际 Fork 使用同一个 prepared identity,避免三次解析得到不同 runtime。
historical Codex capability cache 只能复用 exact launch fingerprint:prepared command、 cwd、env 以及已解析 executable 的 size/mtime/verified identity 都必须一致。无法证明 executable identity 的 shell/package-manager wrapper 不缓存。CLI 升降级或不同 Session runtime identity 会重新 initialize probe,避免跨 Session 误展示按钮。
7. Provider Driver、Codex、Tutti Agent 与 Claude SDK 接入
provider-neutral输入:
source provider Session id
source canonical Turn id
ordered source provider Turn ID prefix
frozen target title
driver kind/version
frozen runtime preparation
Codex Driver:
- 只在 app-server 版本达到已验证门槛时声明支持;
- 调用 typed
thread/fork; - 将 boundary 对应的 provider Turn ID 传为
lastTurnId; - 校验响应 thread ID 非空且不同于 source;
- 校验
forkedFromId与 source 匹配; - 校验返回的全部 provider Turn IDs 与 Store 冻结的 prefix 长度、顺序和值完全一致;
- 任一缺失、重复、截断或额外 Turn 都视为未验证,不提交 canonical child。
- Host 在 accepted checkpoint 前调用 provider-state binder;Codex binder 从 source
CODEX_HOME精确查找 child rollout,完整校验每条 JSONL、session_meta.id、文件 size/SHA-256,只将该 rollout crash-durable 地原子复制到 targetCODEX_HOME,不复制 整个 provider home,也不建立依赖 source 生命周期的软链接。
Tutti Agent Driver 复用同一 app-server typed thread/read / thread/fork(lastTurnId)
协议和 Turn binding JSON,但不冒充 Codex 版本:能力由 tutti_agent/<version> 独立证明,
当前门槛为 0.0.10。provider-state binder 从 source TUTTI_AGENT_HOME 精确复制已验证的
child rollout 到 target TUTTI_AGENT_HOME,不会写入 CODEX_HOME。
provider-state binding 也是 provider 接入能力的一部分。每个 Agent 必须显式选择
host_copy 或 provider_owned 并提交相应 evidence;host_copy binder 缺失或不支持当前
provider 时必须在 provider dispatch 前 fail closed,provider 调用后的 binder 失败进入
unknown;缺少 mapping 或 receipt 时不能进入 provider_accepted。
这比只验证最后一个 Turn 更严格:最后一个 ID 相同不能证明中间历史没有丢失或重排。
provider-state binding 幂等,但不能只凭同 ID rollout 信任 target:source/target fingerprint
完全相等时直接成功;完整验证后的 target 若以 source 全部字节为精确前缀,则视为 Fork 后
合法追加并保留;target 若仅为 source 的精确前缀,则视为截断并从 source 修复;其他合法但
非前缀的分叉 fail closed。source rollout 已被清理时,只接受完整验证通过的 target。若
provider child 已经创建但 binding 失败,operation 进入 unknown,不提交 canonical child,
也绝不重发 provider Fork。
Claude SDK Driver 使用固定版本的官方公开 API:
- Go adapter 在提交前生成的 UUID 仅用于出站 prompt 关联,不作为 provider 身份;
Claude Code 可能在持久化 transcript 时改写 caller UUID,因此 sidecar 必须等待 root
user-message 回显,读取 Claude 实际 UUID 后发出
provider_turn_started,canonicalRootProviderTurnID只接受该已观察身份; - sidecar 为主链 user/assistant/system 消息发出
provider_turn_checkpoint;Claude adapter 的写入 hook 将最新外层 UUID 写进自己的 binding JSON(当前为checkpointMessageId),Store 只做 opaque JSON 持久化;Turn settlement 不冻结 JSON,因为尾随 system message 可能稍后到达; - capability 只声明固定 SDK/sidecar 的结构能力,不读取 transcript。Prepare 将所选
Turn 的
RootProviderTurnID与provider_turn_binding_json一起冻结到 operation; 新 Turn 在 dispatch 时 O(1) 由 Claude adapter 解出 checkpoint。迁移前的旧 Turn JSON 为空时,才读取一次 source transcript,查找所选 origin root user UUID,并取下一个 origin root user 之前的最后消息 UUID;task notification 与内部 synthetic user 不作为新 Turn 边界; - 调用
forkSession(source, {upToMessageId, title: frozenTargetTitle}); - Fork 后只读取 child 的
getSessionInfo/getSessionMessages,确认 child 身份独立可读; 不复读 source,也不比较 Tutti 与 provider 的历史 prefix; - child UUID 会被 SDK 重映射。sidecar 只在 child 尾部定位最后一个真实 origin root user
作为所选 Turn 的新 provider 身份,并从该 Turn 起取最后一个可见消息 UUID 作为 child
checkpoint;它不验证更早历史消息的 UUID 完整性,最终返回只绑定 source/child
session、所选 source/child Turn 与两端 checkpoint 的
provider_ownedreceipt; - Store 在
provider_acceptedcheckpoint 原子保存 mapping/receipt,并在 canonical clone 时重写所选 child Turn 的RootProviderTurnID与 Claude adapter 返回的 binding JSON, 使 fork child 可以继续 Fork; - child 恢复时拒绝 source 的 stale
resumeCursor,只从 canonical childproviderSessionId启动;该约束覆盖进程重启后的首次续聊。
forkSession 调用前失败是 not_started;一旦调用开始,transport、SDK 或 post-verify
失败都是 unknown。Claude provider child UUID 由 SDK 生成,driver 不声明 deterministic
target identity;Host 仍预留 deterministic canonical target Session ID,但绝不重放
unknown provider mutation。这样不会因响应丢失或复验失败而创建第二个 provider child。
损坏 target 的修复规则更严格:只有首条 session_meta 已精确验证 child identity,且包括
partial tail 在内的全部原始字节都是 source rollout 的严格前缀时,才允许从 source 原位
重建。其他损坏状态一律 fail closed 且不覆盖 target;特别是
source baseline + 完整 post-Fork history + partial tail 不得回滚为 source baseline。
7.1 Delivery disposition
runtime 到 Host 使用显式 delivery disposition:
| disposition | 含义 | Host 结果 |
|---|---|---|
not_started |
RPC 前失败,确定未发送 | failed |
rejected |
收到明确 JSON-RPC error response | failed |
unknown |
timeout/disconnect/响应不可验证 | unknown |
accepted |
provider child 已创建且所选 Turn 已重新绑定 | provider_accepted |
明确 provider rejection 不归类为 unknown;反之,RPC 已发送后的 timeout 也不能归类为
普通失败并生成第二个 provider child。accepted 是进入 provider_accepted 的硬门槛;
即使 adapter 返回了非空 child ID,not_started/rejected/unknown/空值 也不能提交
canonical child。
8. Public API
8.1 创建/重放
POST /v1/workspaces/{workspaceID}/agent-sessions/{agentSessionID}/fork
请求:
{
"requestId": "caller-stable-id",
"targetAgentSessionId": "caller-stable-target-id",
"point": {
"type": "throughTurn",
"turnId": "canonical-turn-id"
}
}
一旦 operation 已创建,provider/local 结果通过 typed operation 返回,不再丢成普通 HTTP 错误:
accepted:HTTP 202;committed、failed、unknown:HTTP 200;- operation 创建前的 invalid argument、not found、conflict 等仍为 4xx。
8.2 查询
GET /v1/workspaces/{workspaceID}/agent-session-fork-operations/{operationID}
公开 operation:
interface WorkspaceAgentSessionForkOperation {
operationId: string;
requestId: string;
sourceAgentSessionId: string;
targetAgentSessionId: string;
point: { type: "throughTurn"; turnId: string };
status: "accepted" | "committed" | "failed" | "unknown";
session: WorkspaceAgentSession | null;
lineage: WorkspaceAgentSessionForkLineage | null;
error: string | null;
}
committed 必须携带完整 Session 和 lineage。该结果从 operation 的 immutable snapshot
重建,不依赖 child 当前仍存在,因此 commit 后删除 child 也不会让 operation GET 退化为
502。Activity desktop adapter 对 accepted operation 使用 GET 调和,直到得到 terminal
operation;若调和 transport 失败,则以 delivery-unknown 交回 Engine。
GET 是纯查询,绝不隐式确认 operation。
8.3 Client-observed ACK
POST /v1/workspaces/{workspaceID}/agent-session-fork-operations/{operationID}/acknowledge
ACK 仅接受 committed operation,并且幂等返回同一 immutable operation。Engine
只有在校验 committed result、把 authoritative child Session 纳入 canonical state
以后才发送 ACK。ACK 失败只保留 ackPending 并重试 ACK,不能回滚 child、改写成功状态
或再次调用 provider。服务端在 ACK 事务中更新 client_observed_at 并释放 source + point
barrier;ACK 与新 Fork 并发时由 Store 事务决定顺序。
ACK effect 使用 10 秒 deadline;失败或超时后 Engine 通过自身 expiry clock 按
1s / 2s / 5s / 10s / 30s capped backoff 自动重发相同 operation ID 的幂等 ACK,
不依赖新的 Session event 或用户点击。成功和 Engine dispose 会停止或取消后续 retry;
child/source 的后续删除不会撤销已经成立的 observation,未完成的 ACK 仍继续幂等重试。
unresolved ACK record 不参与普通 mutation 裁剪,也会阻止同 boundary 生成新的 provider
Fork intent,但不会阻止 pin/delete 等无关 Session mutation。
因此,服务端已经 commit 但 POST 响应丢失时,即使 App 重启并生成了新 request/target, 新请求也只会拿回原 committed operation 和原 target。收到并纳入该 child 后 ACK;用户 下一次显式点击才会创建第二个分支。
8.4 Session lineage
所有 Session projection 都有 required + nullable:
interface WorkspaceAgentSessionForkLineage {
sourceAgentSessionId: string;
sourceTurnId: string;
targetTurnId: string;
operationId: string;
forkedAtUnixMs: number;
}
普通 root 和 provider-native child 为 null;Fork root 非 null。该字段贯通 list/detail、 service/API mapper、generated client、Activity normalization 和 GUI。
9. AgentSessionEngine 与 GUI
Engine 的 mutation record 是唯一前端状态源,保存:
- request ID;
- target Session ID;
- operation ID;
- ACK pending/observed 状态;
- source Session/Turn;
inFlight | succeeded | failed | unknown。
行为:
- 新 boundary 首次提交时 Engine 生成 request/target identity;
- inFlight 重复点击不发第二条命令;
- confirmed failed 后的显式新尝试分配新 request/target identity;
- unknown 的重试必须复用原 request/target identity;
- unresolved unknown 不参与普通 128 条 settled mutation 裁剪;若 Engine 重启已丢失前端 identity,Host 会按 source+point 找回 durable unknown operation 并禁止第二次 provider dispatch;
- committed 但未 ACK 的 operation 同样由 source+point barrier 找回;recovered result 必须采用 durable operation 原 request/target/session identity,不能伪装成新 target;
- accepted operation 保持 inFlight,直到 GET 返回 terminal 或 canonical target Session upsert;
- committed operation 原子 upsert authoritative Session,然后 mutation succeeded 并发出 client-observed ACK;ACK 失败只进入 ackPending;
- React 只传 workspace/source/turn,不保存 request ref 或私有 pending map。
GUI:
- 只有
forkThroughTurn=true的 exact Session 显示 Turn Fork 按钮; - 只在 canonical settled root Turn 上提供动作;
- pending Turn 从 Engine selector 派生;
- 成功后打开 authoritative target Session;
- Fork child 不在顶部展示 source Session;时间线在
targetTurnId对应 Turn 完整内容后 展示“接续自任务”分隔线; - 分隔线复用 Turn attachment 的虚拟化定位;边界 Turn 的分页尚未加载时保持隐藏,不能 降级到时间线末尾。
10. Recovery
Host 启动恢复按稳定分页扫描:
prepared:没有 dispatch marker,进程重启时标记为failed并释放 fence/reservation;调用方需要显式发起一次新尝试;dispatching:不盲目重发 provider RPC;按 delivery evidence 进入 unknown;provider_accepted:provider-state binding 已完成,只执行幂等 canonical commit;- terminal operation 不进入恢复队列。
committed + client-unobserved 虽然不需要 provider/canonical 恢复,仍保留 boundary
barrier,直到 Engine 显式 ACK。failed 立即释放;unknown 永久保留,等待未来独立的
人工风险处置协议。
Host 在 provider dispatch 和关键 Store checkpoint 使用独立短 checkpoint context,避免
HTTP caller cancellation 让已创建 operation 丢失状态。POST 超过同步 checkpoint 可以返回
accepted/202;operation GET 在 source actor 内幂等推进 provider_accepted -> committed,
启动恢复负责进程重启后的同一转换,客户端也可通过 canonical Session event 收敛。GET 遇到
遗留 prepared operation 会将其标为 failed;遇到已经写入 dispatch marker、但没有 durable
provider result 的 dispatching operation 会将其标为 unknown。两种情况都不会重新解析 runtime
或触发 provider RPC。
对早期已提交但尚未完成 namespace binding 的 Codex Fork,resume preparation 会根据
canonical lineage 与 committed operation 重新验证 source/target identity,并执行同一个
幂等单-rollout binding;因此升级后的首次继续对话可以自修复,且不会重新调用
thread/fork。后续每次 resume 重复执行 binder 时,target 对 source 的合法追加会被保留,
target 仅为 source 的完整记录前缀时会从 source 修复,其他非前缀分叉 fail closed,避免把
子会话已经追加的新历史覆盖回 Fork 初始状态。若 target 尾部已损坏,则仅在 target 的全部
原始字节仍是 source 的严格前缀时重建;包含完整 post-Fork 历史的损坏 target 保持原样并
fail closed。
11. 当前限制与后续
当前明确不支持:
- 完整会话 Fork 产品入口;
- 除 Codex、Tutti Agent 与 Claude Code 外的其他 Agent Driver;
- 没有原生历史边界能力时的消息 replay 模拟;
- 从 Message 中间位置 Fork;
- 包含 session-local attachment 的 boundary;
- 包含 provider-native descendant lane 的 boundary;
- Git branch/worktree/filesystem snapshot;
- 跨 provider Fork;
- Fork tree/ordinal UI;
- provider accepted 后的人工 repair/abandon 工作流。
下一步接入完整会话 Fork 时:
- 在 Host 为
Point=latestSettled增加明确分支和 Store snapshot 规则; - Driver 单独声明
FullSession=true; - API 扩展 Point union;
- Engine 复用现有 operation/mutation,不新建 saga;
- GUI 只在
fork=true时展示 Session 级入口。
不同 Agent 独立接入:
- 各 Driver 报告真实
FullSession/ThroughTurn; - 能证明 provider/canonical history 一致才置 true;
- 不支持的 Agent 保持 false,GUI 不展示;
- 禁止用品牌名推断或用历史消息 replay 伪造原生支持。
12. 验证要求
发布前至少覆盖:
- Store snapshot、source fence、deterministic ID remap、replay、二次 Fork和 race test;
- Host live/historical RuntimePreparation、re-attestation、recovery 和 disposition;
- Codex exact ordered provider prefix、版本门槛、RPC rejection/timeout;
- Tutti Agent 独立版本门槛、binding hook、
TUTTI_AGENT_HOMEstate binding; - Service/API 200/202/4xx 和 POST/GET typed operation;
- generated API 幂等检查;
- Activity Engine accepted/failed/unknown/committed 和 identity reuse;
- GUI capability visibility、pending、lineage navigation;
- desktop adapter accepted polling;
- Agent Host、provider、renderer 和 AgentGUI 架构边界检查。

