package orchestration import ( "log/slog" "time" "git.yeij.top/AskaEth/Cyrene_v2/internal/bus" "git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/loop" "git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/memory" "git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/orchestration/audit" "git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/orchestration/comm" "git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/trace" ) // 编排层的默认值。 const ( // DefaultMaxParallel 是 Options.MaxParallel 为非正数时同时运行的子代理 // 数量上限。 DefaultMaxParallel = 4 // defaultAgentID 是 Options.AgentID 为空、且必须由 Bus 构建探针时所用的 // comm 探针身份。 defaultAgentID = "orchestrator" ) // Options 配置由 New 构建的 Scheduler。所有字段都可选;每个为 nil 的依赖都优雅 // 降级(缺 Loop 使子代理快速失败,缺 Comm 跳过进度事件,缺 Approver 使 Approve // 报错,等等)。 type Options struct { // MaxParallel 限制同时执行的子代理数量。超出的子代理排队直到有空位。 // 非正数则选用 DefaultMaxParallel(4)。 MaxParallel int // PerAgentTimeout 约束每个子代理的运行时限。超时后该子代理被中止 // (状态 aborted)。非正数表示不设时限。 PerAgentTimeout time.Duration // Loop 是执行模板:用于运行每个子代理的 *loop.Loop。它必须能安全地并发调用 // Run(如 loop.Loop 自身文档所述);模板的 agent/tools/guardrails/persona // 适用于每个子代理。为 nil 时子代理以 ErrNoRunner 快速失败。 Loop *loop.Loop // Bus 是进程内事件总线,用于发布进度事件;Comm 为 nil 时也由它构建默认 // comm 探针。为 nil 时不发布任何事件。 Bus bus.Bus // Comm 是用于发布进度事件的 comm 探针。为 nil 时,用 Bus(Bus 非 nil 时) // 构建一个绑定到 AgentID 的探针。 Comm *comm.Probe // Memory 是 SinkMemory 所用的长期记忆存储。可选。 Memory *memory.Store // Tracer 为每次运行发射一个子代理 span(kind subagent)。可选。 Tracer *trace.Tracer // Flow 非 nil 时接收子代理 span 的状态转移,使派生出的分支出现在 // AgentSpec.ParentSession 所指会话的流程图里(design/19 §2)。 // *loop.FlowPublisher 是生产实现。可选:sink 为 nil 时子代理分支不被可视化 // (子代理自身的内部 span 仍会经循环的 flow 分支折进父图)。 Flow FlowSpanSink // Approver 记录由 Approve 触发的敏感操作「代审」。策略判断属于上层。可选。 Approver *audit.Approver // PersonaExists 报告某个角色 id 能否以自身指令运行,即 Loop 背后的按角色 // agent 池(loop.Options.Agents)里是否有它的条目(design/22 §8-2)。它把守 // AgentSpec.PersonaID:指名了一个它返回 false 的 id 的派发会被拒绝并返回 // ErrUnknownPersona,而不是按默认角色的提示词运行。为 nil 意味着「本部署不认识 // 任何角色」,因此指名角色的派发同样被拒 —— 关键是绝不悄悄替调用方做决定。 // 省略 persona 的派发不受它影响。 PersonaExists func(personaID string) bool // Logger 是基础 logger。为 nil 时替换为标注了来源 "orchestration" 的 // 丢弃式 logger。 Logger *slog.Logger // AgentID 是 comm 探针身份。为空且必须由 Bus 构建探针时,使用 // "orchestrator"。 AgentID string } // FlowSpanSink 把一个 span 的状态转移折进会话的实时流程图,使实时可视化展示在飞的 // 工作(design/19 §1)。*loop.FlowPublisher 是生产实现;测试替换为捕获式假实现。 // 该接口让编排层除了已有的循环模板之外,不必再依赖那个具体发布器。 type FlowSpanSink interface { // SpanStarted 为一个刚刚开始的 span 登记运行中的占位节点。 SpanStarted(sessionID string, sp *trace.Span) // SpanFinished 把一个刚结束的 span 折进会话图。 SpanFinished(sessionID string, sp *trace.Span) } // wrapLoop 把 *loop.Loop 适配成 runner 接口;模板为 nil 时替换为空操作 runner // (快速失败)。 func wrapLoop(l *loop.Loop) runner { if l == nil { return noopRunner{} } return l } // scopedRunner 挑选执行 sub 的 runner:没有工具白名单时按原样运行已配置的模板 // (缺省全开,继承主 agent 全量工具集);有白名单时,为 *loop.Loop 模板经 // WithToolWhitelist 派生出只放行所列工具的新模板(design/05 §7 裁决 02.4.1)。 // 非 loop 的 runner(测试注入的 mock)原样返回 —— 白名单仍随运行上下文传递,它可经 // loop.ToolWhitelistFrom 自行读取。 func (s *Scheduler) scopedRunner(sub *SubAgent) runner { if len(sub.ToolWhitelist) == 0 { return s.runner } if lp, ok := s.runner.(*loop.Loop); ok { return lp.WithToolWhitelist(sub.ToolWhitelist) } return s.runner } // effectiveTimeout 解析某个子代理运行时所受的时限:正的按调用覆盖优先;否则套用 // 调度器的 PerAgentTimeout(非正 = 无期限)。 func effectiveTimeout(override, schedulerDefault time.Duration) time.Duration { if override > 0 { return override } return schedulerDefault }