design/22 §8-2(主人 2026-10-03 拍板):跨角色协作 = 把其他角色以子代理形式派出去,
该子代理以目标角色的 instructions 运行、并按目标角色的记忆作用域读写。
- AgentSpec / SubAgent / SubAgentResult 新增 PersonaID;Options.PersonaExists 作角色解析器
- scheduler:派发前校验角色(ErrUnknownPersona),批量 all-or-nothing;
run ctx 经 withPersonaEnv 只覆盖 SessionEnv.PersonaID —— ActorEnv/MemWritable 原样复制
(身份识别靠 channel 元数据,persona 决定她怎么答、不决定她能碰什么:项目红线)
- 记忆归属键改为 PersonaID 优先(回落 RoleID)→ 替班者写的是目标角色的记忆域
- spawntool:schema 顶层与 tasks[] 新增可选 persona;省略 = 与旧行为逐字节一致
- 分支根节点新增 props.persona(可观测性);SinkMemory 移入 memory.go(守 500 行红线)
- 组合根:personaExists(agent 池);cmd/cyrene/wire.go +5 行
契约:Docs/api-client.md -> v0.47(graph_update 新增可选 props.persona,纯增量)
验证:go build/vet ./... 绿;internal/runtime/orchestration + cmd/cyrene 测试绿;
go test -race ./internal/runtime/orchestration -run Persona 绿;
linux/amd64 + darwin/arm64 交叉编译绿
变异验证:① 目标角色换回父/默认角色(3 处断言 FAIL)② 关调度器侧角色校验 ③ 关工具侧预检
100 lines
4.2 KiB
Go
100 lines
4.2 KiB
Go
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"
|
|
)
|
|
|
|
// Defaults for the orchestration layer.
|
|
const (
|
|
// DefaultMaxParallel is the maximum number of sub-agents that run at once
|
|
// when Options.MaxParallel is non-positive.
|
|
DefaultMaxParallel = 4
|
|
|
|
// defaultAgentID is the comm probe identity used when Options.AgentID is
|
|
// blank and a probe must be built from Bus.
|
|
defaultAgentID = "orchestrator"
|
|
)
|
|
|
|
// Options configures a Scheduler built by New. All fields are optional; each nil
|
|
// dependency degrades gracefully (a missing Loop makes sub-agents fail fast, a
|
|
// missing Comm skips progress events, a missing Approver makes Approve error,
|
|
// and so on).
|
|
type Options struct {
|
|
// MaxParallel caps how many sub-agents execute concurrently. Excess sub-agents
|
|
// queue until a slot frees. Non-positive selects DefaultMaxParallel (4).
|
|
MaxParallel int
|
|
|
|
// PerAgentTimeout bounds every sub-agent's run. When it elapses the sub-agent
|
|
// is aborted (status aborted). Non-positive means no timeout.
|
|
PerAgentTimeout time.Duration
|
|
|
|
// Loop is the execution template: a *loop.Loop used to run every sub-agent.
|
|
// It must be safe for concurrent Run calls (as loop.Loop documents); the
|
|
// template's agent/tools/guardrails/persona apply to every sub-agent. When
|
|
// nil, sub-agents fail fast with ErrNoRunner.
|
|
Loop *loop.Loop
|
|
|
|
// Bus is the in-process event bus used for progress events and, when Comm is
|
|
// nil, to build a default comm probe. When nil no events are published.
|
|
Bus bus.Bus
|
|
|
|
// Comm is the comm probe used to publish progress events. When nil, a probe
|
|
// bound to AgentID is built from Bus (when Bus is non-nil).
|
|
Comm *comm.Probe
|
|
|
|
// Memory is the long-term memory store used by SinkMemory. Optional.
|
|
Memory *memory.Store
|
|
|
|
// Tracer emits a per-sub-agent span (kind subagent) around each run. Optional.
|
|
Tracer *trace.Tracer
|
|
|
|
// Flow, when non-nil, receives the sub-agent span transitions so a spawned
|
|
// branch appears in the flow graph of the session named by
|
|
// AgentSpec.ParentSession (design/19 §2). *loop.FlowPublisher is the
|
|
// production implementation. Optional: a nil sink leaves sub-agent branches
|
|
// unvisualized (the sub-agent's own inner spans still fold into the parent
|
|
// graph through the loop's flow branch).
|
|
Flow FlowSpanSink
|
|
|
|
// Approver records sensitive-operation review-and-approve (代审) triggered by
|
|
// Approve. Policy judgement belongs to the upper layer. Optional.
|
|
Approver *audit.Approver
|
|
|
|
// PersonaExists reports whether a role id can run with its own instructions,
|
|
// i.e. whether the per-role agent pool behind Loop (loop.Options.Agents) has an
|
|
// entry for it (design/22 §8-2). It gates AgentSpec.PersonaID: a dispatch naming
|
|
// an id this returns false for is rejected with ErrUnknownPersona rather than
|
|
// running as the default role's prompt. Nil means "this deployment knows no
|
|
// role", so a dispatch that names a role is rejected too — deciding nothing
|
|
// silently is the point. Omitting the persona is unaffected by this.
|
|
PersonaExists func(personaID string) bool
|
|
|
|
// Logger is the base logger. Nil is replaced with a discard logger tagged with
|
|
// source "orchestration".
|
|
Logger *slog.Logger
|
|
|
|
// AgentID is the comm probe identity. When blank and a probe must be built
|
|
// from Bus, "orchestrator" is used.
|
|
AgentID string
|
|
}
|
|
|
|
// FlowSpanSink folds a span transition into a session's live flow graph so the
|
|
// realtime visualization shows the work in flight (design/19 §1). *loop.FlowPublisher
|
|
// is the production implementation; tests substitute a capturing fake. The
|
|
// interface keeps orchestration from depending on the concrete publisher beyond
|
|
// the loop template it already takes.
|
|
type FlowSpanSink interface {
|
|
// SpanStarted registers a running placeholder node for a span that just began.
|
|
SpanStarted(sessionID string, sp *trace.Span)
|
|
// SpanFinished folds a just-finished span into the session graph.
|
|
SpanFinished(sessionID string, sp *trace.Span)
|
|
}
|