feat(gateway,loop): 忙时并行回合基础管线(I1′ 有条件放宽)
- turnId 由编排器生成、loop Run 复用(硬前提:否则 turn_begin 与流帧各铸一个,客户端两级分桶立即失配) - StateSink 增带 turnId 的变体(未接线时退化为现签名,不丢状态帧) - 上行 chat.parallel(WS chat 帧 + REST 三入口同形;省略 = 逐字不变) - 单会话额度默认 2 / 硬上限 3 / <2 关闭;额度满或未声明 ⇒ 落回既有排队语义 - I1′:checkpoint 归属、历史配对、忙态计数三处从会话级单例升为路级 - session_idle 收紧为「所有在途路收尾且 pending 空」(仅并行时生效;单路逐字不变) - 预留接口(形状定死、无实现无接线):tool/legfile.go 的 FileClaimer / ErrFileBusy / WithLegID - 顺手修一个真 bug:并行路 ctx 派生自提交请求作用域,REST 同步入口一写响应就把它连坐杀死(改 context.WithoutCancel 派生) - 23 个新用例(含 -race -count=3);变异 6 条(turnId 复用 / idle 时点 / 并行升级 / checkpoint 键 / 编排器交 id / pending_ack reason) - 契约变更点 10 条待监工升 v0.65(本人未改 Docs/**)
This commit is contained in:
1 parent
7d2ff74cfc
commit
89e13cf53d
23 files changed
+1921
-275
No files matched your search
@@ -33,12 +33,29 @@ type checkpointState struct {
|
||||
StopReason string `json:"stop_reason,omitempty"`
|
||||
}
|
||||
|
||||
// checkpointAgentID 返回本回合 checkpoint 的归属键(design/15 §2.5.9)。
|
||||
//
|
||||
// 单路(ctx 上未挂并发路标识)时它逐字等于 sessionID —— 与加入并发路之前
|
||||
// 完全一致,归属语义不变;真并行路(ctx 上挂了 legID)时升为
|
||||
// `{sessionID}#{legID}`,使 LoadLatest 按路隔离:并行的两路各写各的窗口,
|
||||
// 绝不会把对方的快照当成自己的续接(并发写同一个 key 会互相覆盖)。
|
||||
//
|
||||
// 归属键同时用作 checkpoint 行 id 的前缀,因此两路的行在 `checkpoints` 表里
|
||||
// 天然不冲突。
|
||||
func checkpointAgentID(ctx context.Context, sessionID string) string {
|
||||
if leg := ParallelLegFrom(ctx); leg != "" {
|
||||
return sessionID + "#" + leg
|
||||
}
|
||||
return sessionID
|
||||
}
|
||||
|
||||
// saveCheckpoint 在 step 处持久化一个快照。它绝不让 run 失败:
|
||||
// store 为 nil 时为空操作,marshal/save 出错则记录日志。
|
||||
func (l *Loop) saveCheckpoint(ctx context.Context, sessionID string, step int, messages []model.Message, stopReason string) {
|
||||
if l.checkpoint == nil {
|
||||
return
|
||||
}
|
||||
owner := checkpointAgentID(ctx, sessionID)
|
||||
state := checkpointState{
|
||||
Step: step,
|
||||
Messages: messages,
|
||||
@@ -50,8 +67,8 @@ func (l *Loop) saveCheckpoint(ctx context.Context, sessionID string, step int, m
|
||||
return
|
||||
}
|
||||
cp := &persist.Checkpoint{
|
||||
ID: fmt.Sprintf("%s:%d", sessionID, step),
|
||||
AgentID: sessionID,
|
||||
ID: fmt.Sprintf("%s:%d", owner, step),
|
||||
AgentID: owner,
|
||||
Step: step,
|
||||
State: buf,
|
||||
}
|
||||
@@ -79,10 +96,13 @@ func (l *Loop) loadCheckpoint(ctx context.Context, sessionID string, messages []
|
||||
if l.checkpoint == nil {
|
||||
return messages, 1, false
|
||||
}
|
||||
cp, err := l.checkpoint.LoadLatest(ctx, sessionID)
|
||||
// 归属键按路取(design/15 §2.5.9):单路 = sessionID(逐字不变),真并行路
|
||||
// = {sessionID}#{legID},因此两路的崩溃快照互不可见,绝不会互相顶掉。
|
||||
owner := checkpointAgentID(ctx, sessionID)
|
||||
cp, err := l.checkpoint.LoadLatest(ctx, owner)
|
||||
if err != nil {
|
||||
if !errors.Is(err, persist.ErrNotFound) {
|
||||
l.log.Warn("checkpoint load", "session", sessionID, "error", err)
|
||||
l.log.Warn("checkpoint load", "session", sessionID, "agent", owner, "error", err)
|
||||
}
|
||||
return messages, 1, false
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user