refactor(dispatcher): compose 翻默认 —— 补平三缺口后退役 graph.go 在望

EINO_COMPOSE 默认改为开(留 =0 逃生舱回退权威 graph.go)。翻默认前补平
compose 路径三个会静默吃掉治理能力的缺口:

- HITL 审批:execDSLNode 无 approval 分支 → 审批节点被当未识别跳过、下游照跑;
  补 case 调 approvalNode。
- 忠实度评测:executeGraph 把 refs 硬写 nil → 评测静默失效;runComposeGraph
  改签名回传 refsOf(b)。
- 终态传播:rejected/fatalErr 不传播也不阻断下游 → 任务误判 done-空;加节点
  lambda 入口守卫 + branch cond 守卫 + 终态上抛 errRejected/fatalErr,并让
  runComposeConversation 模型失败置 fatalErr(对齐 graph.go 的 break 语义)。

新增等价测试:审批拒绝停下游、refs 回流。go test ./... 全绿。
soak 无回归后即可物理退役 graph.go。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Blizzard
2026-06-29 11:13:46 +08:00
parent d2662a1f37
commit 03225e31a9
3 changed files with 103 additions and 13 deletions
@@ -14,9 +14,10 @@ import (
"github.com/sundynix/sundynix-dispatcher/internal/harness"
)
// composeEnabled 报告是否启用 compose.Graph 编排路径(Phase C 灰度开关,默认关 → 走自研 graph.go)
// 并存策略:EINO_COMPOSE=1 时对话主流程改走 Eino compose 运行时,行为对齐后再逐步退役 graph.go。
func composeEnabled() bool { return os.Getenv("EINO_COMPOSE") == "1" }
// composeEnabled 报告是否启用 compose.Graph 编排路径。
// Phase C 灰度收尾:compose 已与 graph.go 终态对齐(refs / 审批拒绝 / 预算·模型失败 全部传播),
// 默认翻为 compose 运行时;保留逃生舱 EINO_COMPOSE=0 → 回退权威 graph.go。soak 无回归后即可退役 graph.go。
func composeEnabled() bool { return os.Getenv("EINO_COMPOSE") != "0" }
// runConversation 是对话/模型节点的统一入口:按灰度开关选 compose.Graph 或自研 runAgent。
// 二者对外行为一致(据黑板拼消息 → 流式回流 token → 累计成稿),便于等价回归。
@@ -82,6 +83,9 @@ func (o *Orchestrator) runComposeConversation(ctx context.Context, taskID string
sr, err := r.Stream(ctx, msgs, compose.WithCallbacks(composeTracer(tr, node)))
if err != nil {
tr.emit(node, "model", "error", "compose 图执行", err.Error(), time.Since(t0).Milliseconds())
if b.fatalErr == nil { // 模型失败 → 标记致命错,让任务判 failed(对齐 runAgent,杜绝 done-空)
b.fatalErr = fmt.Errorf("agent 模型推理失败: %w", err)
}
return
}
defer sr.Close()
@@ -104,6 +108,9 @@ func (o *Orchestrator) runComposeConversation(ctx context.Context, taskID string
}
if rerr != nil {
tr.emit(node, "model", "error", "compose 图执行", rerr.Error(), time.Since(t0).Milliseconds())
if b.fatalErr == nil { // 流式中断也算失败(结果不完整)→ 判 failed,不静默 done-空
b.fatalErr = fmt.Errorf("agent 模型推理失败: %w", rerr)
}
return
}
if chunk.Content == "" {