fix(gateway,loop,tool): 解耦「工具互斥身份」与「checkpoint 归属」(修普通回合不受保护)
缺口:「1 普通回合 + 1 并行路」时普通回合既不登记也不被检查 —— 因为互斥身份取自 loop.ParallelLegFrom,而网关只给 parallel=true 那一路盖它。直接给普通回合盖 WithParallelLeg 会把它的 checkpoint 键从 sess 变成 sess#turn-x(改崩溃恢复语义),故改为两条独立通道: - 工具互斥身份:loop.WithToolLegID / ToolLegIDFrom(新增),网关在 beginLegLocked 内【无条件】盖, 取值 = 本路 turnID ⇒ 普通回合 / 队首续跑路 / 并行路 三条路都覆盖 - checkpoint 归属:WithParallelLeg 一行未改,仍只有真并行路 ⇒ 恢复语义零变化 守门断言 TestCheckpointOwnerIgnoresToolLegID:普通回合 ctx 下 AgentID 必须逐字仍是 sess。 变异 3 条:互斥身份回退只认 ParallelLegFrom(判忙失效)/ 顺手把 checkpoint 改读新通道 (守门断言红,证明确实解耦)/ 普通回合不盖身份(判忙失效)。 另修 race:-race -count=2 三包全绿;诚实边界含一个既有竞态(同一路并发登记共享,建议后续小切片)。
This commit is contained in:
1 parent
d64b26d499
commit
ef3dcb699b
9 files changed
+673
-53
No files matched your search
@@ -299,10 +299,16 @@ func (l *Loop) invokeOne(ctx context.Context, d *tool.Dispatcher, tc agent.ToolC
|
||||
}
|
||||
_, toolSpan := l.span(ctx, "tool."+tc.Name, trace.SpanKindTool)
|
||||
l.emitToolExecution(ctx, tc, model.StreamKindToolExecutionStart, model.ToolPhaseRunning)
|
||||
// 路标识桥(design/15 §2.5.7、主人裁决 3):网关把「本回合属于哪条并发路」经
|
||||
// loop.WithParallelLeg 挂在 ctx 上,工具层按 tool.WithLegID 读它做文件级互斥
|
||||
// (工具侧唯一来源);未挂载 ⇒ 空串 ⇒ 完全不做互斥,行为逐字不变。
|
||||
result, err := d.Invoke(tool.WithLegID(ctx, ParallelLegFrom(ctx)), tc.Name, tc.Args)
|
||||
// 路标识桥(design/15 §2.5.7、主人裁决 3):网关在**每一条在途路**上把互斥
|
||||
// 身份经 loop.WithToolLegID 挂在 ctx 上(普通回合 / 并行路 / 队首续跑路都挂,
|
||||
// 取值 = 该路的 turnID),这里把它桥接成工具层的 tool.WithLegID —— 工具侧
|
||||
// 凭 tool.LegIDFrom 读到「我是哪一路」做文件级互斥;未挂载 ⇒ 空串 ⇒ 完全
|
||||
// 不做互斥,行为逐字不变(兼容硬要求)。
|
||||
//
|
||||
// 刻意**不**读 ParallelLegFrom:那条通道只有真并行路才有、且只决定 checkpoint
|
||||
// 归属(checkpoint.go 的 checkpointAgentID),拿它当互斥身份会让普通回合在
|
||||
// 「1 普通回合 + 1 并行路」时完全不受保护。
|
||||
result, err := d.Invoke(tool.WithLegID(ctx, ToolLegIDFrom(ctx)), tc.Name, tc.Args)
|
||||
phase := model.ToolPhaseDone
|
||||
if err != nil {
|
||||
phase = model.ToolPhaseError
|
||||
|
||||
Reference in new issue
Block a user