docs(comments): 注释中文化 · internal/runtime/orchestration(29 文件,纯替换)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled

- 只翻注释/文档字符串:剥离注释后代码行逐字节不变(机械校验只剩允许的行尾注释形态)
- 裁决编号与 design 节号(02.4.3 / 11.4.2 / design/05 §9 等)逐字保留
- 复核:go build 绿;orchestration 及其子包(audit/comm)测试绿;gofmt 干净
This commit is contained in:
AskaEth committed 2026-10-04 13:30:04 +08:00
1 parent fe90f64a56
commit eb1828ee7a
29 files changed
+751 -949

No files matched your search

+78 -99
View File
@@ -12,28 +12,23 @@ import (
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/loop"
)
// Constants for the sub-agent dispatch tool (design/05 §1 创建, §7 并行与安全).
// 子代理派发工具(design/05 §1 创建, §7 并行与安全)的常量。
const (
// SpawnToolName is the registered name of the tool the agent calls to
// dispatch background sub-agents.
// SpawnToolName 是 agent 调用以派发后台子代理的工具的注册名。
SpawnToolName = "subagent_spawn"
// DefaultSpawnWait is how long a foreground dispatch waits for its batch
// before handing the turn back to the model. It mirrors the runtime's
// established "前台运行,超过 2 秒自动移入后台" threshold
// (DevDocs/design/04-运行时.md §8.5, internal/runtime/task). The sub-agents keep
// running either way; only whether the tool result already carries their
// output changes.
// DefaultSpawnWait 是前台派发在把回合交还模型之前等待其批次的时间。它沿用运行时
// 既定的「前台运行,超过 2 秒自动移入后台」阈值
// (DevDocs/design/04-运行时.md §8.5, internal/runtime/task)。子代理无论如何都
// 继续运行;变的只是工具结果是否已带上它们的输出。
DefaultSpawnWait = 2 * time.Second
// maxSpawnBatch caps how many sub-agents one dispatch may create. It bounds the
// queue a single model call can build (design/05 §7 防失控); the parallel
// execution limit itself stays with the scheduler's MaxParallel.
// maxSpawnBatch 限制一次派发最多创建多少个子代理。它约束单次模型调用能堆积的
// 队列长度(design/05 §7 防失控);并行执行上限本身仍由调度器的 MaxParallel 负责。
maxSpawnBatch = 8
// maxSpawnTextRunes caps how many runes of one sub-agent's result text are
// handed back to the spawning model, so a runaway sub-agent cannot flood the
// front session's context. The scheduler keeps the full text on its snapshot.
// maxSpawnTextRunes 限制交回派发模型的单个子代理结论文本的字符数,使失控的子代理
// 无法淹没前台会话的上下文。调度器在其快照上保留完整文本。
maxSpawnTextRunes = 4000
// minSpawnTimeoutSeconds 与 maxSpawnTimeoutSeconds 界定按调用覆盖
@@ -50,14 +45,12 @@ const (
SubagentResultSubject = "subagent.result"
)
// SpawnTool is the runtime entry point that lets the running agent dispatch
// sub-agents (design/05 §1). It is the only production caller of
// (*Scheduler).Spawn: the model invokes it like any other tool, the scheduler
// applies the parallel limit, per-agent timeout and abort machinery, and the tool
// hands back either the merged result or — when the batch is still running after
// the foreground wait — a dispatch acknowledgement.
// SpawnTool 是让运行中的 agent 派发子代理的运行时入口(design/05 §1)。它是
// (*Scheduler).Spawn 唯一的生产调用方:模型像调用任何其他工具一样调用它,调度器施加
// 并行上限、按代理时限与中止机制,工具则交回合并结果,或者 —— 前台窗口内批次仍在跑时
// —— 一条派发确认。
//
// It is safe for concurrent use and holds no per-call state.
// 它可安全并发使用,且不持有任何按调用的状态。
type SpawnTool struct {
sched *Scheduler
wait time.Duration
@@ -65,11 +58,10 @@ type SpawnTool struct {
log *slog.Logger
}
// SpawnOption customizes a SpawnTool built by NewSpawnTool.
// SpawnOption 定制由 NewSpawnTool 构建的 SpawnTool。
type SpawnOption func(*SpawnTool)
// WithSpawnWait overrides how long a foreground dispatch waits for its batch. A
// non-positive value keeps DefaultSpawnWait.
// WithSpawnWait 覆盖前台派发等待其批次的时间。非正值保持 DefaultSpawnWait。
func WithSpawnWait(d time.Duration) SpawnOption {
return func(t *SpawnTool) {
if d > 0 {
@@ -78,7 +70,7 @@ func WithSpawnWait(d time.Duration) SpawnOption {
}
}
// WithSpawnLogger replaces the tool's logger (tagged source "orchestration/spawn").
// WithSpawnLogger 替换该工具的 logger(标注来源 "orchestration/spawn")。
func WithSpawnLogger(l *slog.Logger) SpawnOption {
return func(t *SpawnTool) { t.log = spawnLog(l) }
}
@@ -95,8 +87,8 @@ func WithSpawnTimeout(d time.Duration) SpawnOption {
}
}
// NewSpawnTool builds the dispatch tool over sched. A nil scheduler is tolerated:
// Execute then fails fast with ErrNoScheduler instead of panicking.
// NewSpawnTool 在 sched 上构建派发工具。调度器为 nil 也被容忍:Execute 随后以
// ErrNoScheduler 快速失败,而不是 panic。
func NewSpawnTool(sched *Scheduler, opts ...SpawnOption) *SpawnTool {
t := &SpawnTool{sched: sched, wait: DefaultSpawnWait, log: spawnLog(nil)}
for _, opt := range opts {
@@ -107,12 +99,11 @@ func NewSpawnTool(sched *Scheduler, opts ...SpawnOption) *SpawnTool {
return t
}
// Name returns the tool identifier.
// Name 返回工具标识符。
func (t *SpawnTool) Name() string { return SpawnToolName }
// Description explains the tool to the model: what a sub-agent is, that it runs in
// an isolated context in parallel, and that a slow batch is handed to the
// background rather than blocking the conversation (design/05 §1/§2).
// Description 向模型解释该工具:什么是子代理、它在隔离上下文中并行运行、慢批次会被
// 交给后台而不阻塞对话(design/05 §1/§2)。
func (t *SpawnTool) Description() string {
return "Dispatch one or more sub-agents to work in parallel on independent parts of a task. " +
"Each sub-agent runs in its own isolated context and returns a summary plus its result. " +
@@ -120,11 +111,9 @@ func (t *SpawnTool) Description() string {
"results later; prefer several sub-agents over one serial chain for independent work."
}
// Parameters returns the input JSON Schema: either a single goal shorthand or a
// tasks array for a parallel batch, plus an optional persona (the role the
// sub-agent runs as, design/22 §8-2), an optional background flag, an optional
// per-dispatch tool whitelist (design/05 §7) and an optional per-call timeout
// override.
// Parameters 返回输入的 JSON Schema:单个 goal 简写,或用于并行批次的 tasks 数组,
// 外加可选的 persona(子代理运行时所充当的角色,design/22 §8-2)、可选的 background
// 标志、可选的按派发工具白名单(design/05 §7)与可选的按调用超时覆盖。
func (t *SpawnTool) Parameters() (map[string]any, error) {
return map[string]any{
"type": "object",
@@ -149,15 +138,13 @@ func (t *SpawnTool) Parameters() (map[string]any, error) {
}, nil
}
// Execute dispatches the requested sub-agents, then either returns their merged
// results (the batch settled inside the foreground window) or a dispatch
// acknowledgement (it moved to the background, reported later on the bus).
// Execute 派发所请求的子代理,随后要么返回它们的合并结果(批次在前台窗口内结算),
// 要么返回一条派发确认(批次已转入后台,稍后经总线报告)。
//
// The sub-agents are spawned on a detached context: their lifetime is their own
// (bounded by the scheduler's per-agent timeout and Abort/CancelAll), never the
// dispatching turn's, so a slow batch is not killed the moment the tool returns
// (design/04 §2 后台不阻塞前台). Context values are kept so the sub-agent span —
// and the flow lane it roots — still chains under the step that dispatched it.
// 子代理在一个脱离的上下文上派发:它们的生命周期属于自己(受调度器的按代理时限与
// Abort/CancelAll 约束),从不是派发回合的,因此慢批次不会在工具返回的那一刻被杀
// (design/04 §2 后台不阻塞前台)。上下文值被保留,使子代理 span —— 以及它作为根的
// 泳道 —— 仍串在派发它的那一步之下。
func (t *SpawnTool) Execute(ctx context.Context, args any) (any, error) {
m, ok := args.(map[string]any)
if !ok {
@@ -170,9 +157,8 @@ func (t *SpawnTool) Execute(ctx context.Context, args any) (any, error) {
if err != nil {
return nil, fmt.Errorf("%s: %w", SpawnToolName, err)
}
// Reject an unknown target role before anything starts: a batch must be
// all-or-nothing, otherwise the sub-agents already dispatched would keep running
// with their result never reported to the model (design/22 §8-2: 未知角色明确报错,
// 在任何东西启动之前就拒绝未知的目标角色:批次必须是全有或全无,否则已经派发出的
// 子代理会继续运行,而它们的结果永远不上报给模型(design/22 §8-2: 未知角色明确报错,
// 不静默回落到默认角色).
if perr := t.sched.CheckPersonas(specs); perr != nil {
return nil, fmt.Errorf("%s: %w", SpawnToolName, perr)
@@ -197,9 +183,8 @@ func (t *SpawnTool) Execute(ctx context.Context, args any) (any, error) {
return t.foreground(ids, specs), nil
}
// foreground waits up to t.wait for the dispatched batch to settle. A settled
// batch is merged into the tool result; a batch still running when the window
// closes is announced in the background and acknowledged immediately.
// foreground 至多等待 t.wait 让已派发的批次结算。结算的批次被合并进工具结果;窗口
// 关闭时仍在运行的批次改到后台宣告,并立即被确认。
func (t *SpawnTool) foreground(ids []string, specs []AgentSpec) any {
waitCtx, cancel := context.WithTimeout(context.Background(), t.wait)
defer cancel()
@@ -212,9 +197,8 @@ func (t *SpawnTool) foreground(ids []string, specs []AgentSpec) any {
return doneSpawnResult(res)
}
// await blocks until the batch settles and broadcasts the merged outcome as the
// design/05 §2 "结束后写执行总结 → 返回主会话" hand-off. It runs on a detached
// goroutine; a nil bus or an unknown id ends it quietly.
// await 阻塞到批次结算,并把合并结果作为 design/05 §2「结束后写执行总结 → 返回主会话」
// 的交接广播出去。它运行在脱离的 goroutine 上;总线为 nil 或 id 未知时安静结束。
// parentSession 是该批次所属的父会话,随载荷一起广播,使中继能把回填结果
// 归属到父会话的实时流,而不是发一张无归属的帧。
func (t *SpawnTool) await(ids []string, parentSession string) {
@@ -235,36 +219,35 @@ func (t *SpawnTool) await(ids []string, parentSession string) {
}
}
// SpawnResult is the payload the dispatch tool returns to the spawning model: the
// batch's merged outcome when it settled inside the foreground window, or a
// dispatch acknowledgement when it moved to the background.
// SpawnResult 是派发工具返回给派发模型的载荷:批次在前台窗口内结算时是它的合并结果,
// 转入后台时是一条派发确认。
type SpawnResult struct {
// Status is "done" (settled) or "running" (dispatched to the background).
// Status 是 "done"(已结算)或 "running"(已转入后台)。
Status string `json:"status"`
// Summary is a one-line aggregate over the batch.
// Summary 是对该批次的单行聚合。
Summary string `json:"summary"`
// Agents holds one entry per dispatched sub-agent, in dispatch order.
// Agents 持有每个已派发子代理的一个条目,按派发顺序排列。
Agents []SpawnAgentResult `json:"agents"`
}
// SpawnAgentResult is one sub-agent's entry in a SpawnResult.
// SpawnAgentResult 是 SpawnResult 中单个子代理的条目。
type SpawnAgentResult struct {
// ID is the sub-agent id (also the id the WS subagent_event frames carry).
// ID 是子代理 id(也是 WS subagent_event 帧所携带的 id)。
ID string `json:"id"`
// Goal is the objective the sub-agent was given.
// Goal 是交给该子代理的目标。
Goal string `json:"goal"`
// Role is the bound role/persona, if any: the dispatched target persona when one
// was named, otherwise the legacy role label.
// Role 是绑定的角色/persona(若有):指名了派发目标角色时用该角色,否则是旧的
// 角色标签。
Role string `json:"role,omitempty"`
// Mode is the bound behavior mode, if any.
// Mode 是绑定的行为模式(若有)。
Mode string `json:"mode,omitempty"`
// Status is the lifecycle status at return time.
// Status 是返回时刻的生命周期状态。
Status string `json:"status"`
// Summary is the execution summary written when the sub-agent settled.
// Summary 是子代理结算时写入的执行摘要。
Summary string `json:"summary,omitempty"`
// Text is the sub-agent's result text (capped at maxSpawnTextRunes).
// Text 是子代理的结果文本(上限 maxSpawnTextRunes)。
Text string `json:"text,omitempty"`
// Steps is how many loop steps the sub-agent ran.
// Steps 是子代理执行的循环步数。
Steps int `json:"steps,omitempty"`
}
@@ -275,7 +258,7 @@ type SubagentResultEvent struct {
*SpawnResult
}
// doneSpawnResult maps a settled batch onto the tool result.
// doneSpawnResult 把已结算的批次映射为工具结果。
func doneSpawnResult(res *MergedResult) *SpawnResult {
out := &SpawnResult{Status: "done", Agents: make([]SpawnAgentResult, 0)}
if res == nil {
@@ -297,7 +280,7 @@ func doneSpawnResult(res *MergedResult) *SpawnResult {
return out
}
// runningSpawnResult acknowledges a batch that is still running in the background.
// runningSpawnResult 确认一个仍在后台运行的批次。
func runningSpawnResult(ids []string, specs []AgentSpec) *SpawnResult {
out := &SpawnResult{
Status: "running",
@@ -317,10 +300,9 @@ func runningSpawnResult(ids []string, specs []AgentSpec) *SpawnResult {
return out
}
// spawnSpecs decodes the tool arguments into one AgentSpec per sub-agent. A
// non-empty tasks array is a parallel batch and takes precedence over the single
// goal shorthand. dftTimeout is the dispatch-layer default (WithSpawnTimeout);
// a non-positive value leaves the scheduler's per-agent timeout in charge.
// spawnSpecs 把工具参数解码成每个子代理一个 AgentSpec。非空的 tasks 数组是并行批次,
// 优先于单个 goal 简写。dftTimeout 是派发层默认值(WithSpawnTimeout);非正值则让
// 调度器的按代理时限说了算。
func spawnSpecs(m map[string]any, parentSession string, dftTimeout time.Duration) ([]AgentSpec, error) {
tasks, err := taskList(m["tasks"])
if err != nil {
@@ -360,8 +342,8 @@ func spawnSpecs(m map[string]any, parentSession string, dftTimeout time.Duration
return specs, nil
}
// specFromMap reads one AgentSpec out of a decoded arguments object (the top-level
// object or one tasks entry). A blank goal wraps ErrMissingGoal.
// specFromMap 从已解码的参数对象(顶层对象或 tasks 中的一个条目)读取一个 AgentSpec。
// goal 为空时包装 ErrMissingGoal。
func specFromMap(m map[string]any, parentSession string) (AgentSpec, error) {
goal := strings.TrimSpace(stringArg(m, "goal"))
if goal == "" {
@@ -369,11 +351,10 @@ func specFromMap(m map[string]any, parentSession string) (AgentSpec, error) {
}
personaID := strings.TrimSpace(stringArg(m, "persona"))
roleID := strings.TrimSpace(stringArg(m, "role"))
// A named persona is the role the sub-agent actually runs as, so it also fills
// the binding metadata: leaving role and persona disagreeing would make the tool
// result, the progress events and the ws subagent_event frame name a role the
// sub-agent is not answering as. Omitting persona keeps the pre-existing
// role-only path byte for byte (design/22 §8-2 backward compatibility).
// 被指名的 persona 就是子代理实际充当的角色,因此它也填充绑定元数据:让 role 与
// persona 互相矛盾会导致工具结果、进度事件与 ws subagent_event 帧点名一个子代理
// 并未以之作答的角色。省略 persona 则逐字节保持既有的纯 role 路径
// (design/22 §8-2 向后兼容)。
if personaID != "" {
roleID = personaID
}
@@ -389,9 +370,8 @@ func specFromMap(m map[string]any, parentSession string) (AgentSpec, error) {
}, nil
}
// taskList reads a tasks array of objects. A missing or empty array yields nil; a
// non-array or an entry that is not an object is rejected so a malformed batch
// never silently degrades into a single spawn.
// taskList 读取由对象组成的 tasks 数组。缺失或空数组产出 nil;非数组或条目不是对象则
// 被拒绝,使畸形批次绝不静默退化成单次派发。
func taskList(v any) ([]map[string]any, error) {
if v == nil {
return nil, nil
@@ -411,12 +391,12 @@ func taskList(v any) ([]map[string]any, error) {
return out, nil
}
// spawnStringProp builds a string property schema.
// spawnStringProp 构建一个字符串属性 schema。
func spawnStringProp(desc string) map[string]any {
return map[string]any{"type": "string", "description": desc}
}
// spawnTasksProp builds the tasks array property schema for a parallel batch.
// spawnTasksProp 构建并行批次的 tasks 数组属性 schema。
func spawnTasksProp() map[string]any {
return map[string]any{
"type": "array",
@@ -438,8 +418,8 @@ func spawnTasksProp() map[string]any {
}
}
// spawnWhitelistProp builds the tool_whitelist property schema (design/05 §7
// 裁决 02.4.1):缺省全开,显式列出则只放行所列。
// spawnWhitelistProp 构建 tool_whitelist 属性 schema(design/05 §7
// 裁决 02.4.1):缺省全开,显式列出则只放行所列。
func spawnWhitelistProp(desc string) map[string]any {
return map[string]any{
"type": "array",
@@ -452,8 +432,8 @@ func spawnWhitelistProp(desc string) map[string]any {
}
}
// spawnTimeoutProp builds the timeout_seconds property schema (design/05 §7
// 裁决 02.4.4 的按调用覆盖)。dft 非正时描述为「沿用派发默认」。
// spawnTimeoutProp 构建 timeout_seconds 属性 schema(design/05 §7
// 裁决 02.4.4 的按调用覆盖)。dft 非正时描述为「沿用派发默认」。
func spawnTimeoutProp(dft time.Duration) map[string]any {
desc := "Per-call timeout override in seconds for the sub-agent run (optional; omitted keeps the " +
"configured sub-agent default)."
@@ -468,29 +448,28 @@ func spawnTimeoutProp(dft time.Duration) map[string]any {
}
}
// spawnPersonaProp builds the persona property schema. It is the design/22 §8-2 派发
// switch: naming a role makes the sub-agent stand in for it (that role's
// instructions and memory scope) instead of working as the spawning session's role.
// spawnPersonaProp 构建 persona 属性 schema。它是 design/22 §8-2 的派发开关:指名一个
// 角色会让子代理替班该角色(用该角色的指令与记忆作用域),而不是以派发会话的角色工作。
func spawnPersonaProp() map[string]any {
return spawnStringProp("Persona/role id the sub-agent runs AS: it answers with that role's " +
"instructions and reads/writes that role's memory scope (optional, default: the current " +
"role). A role id this runtime does not know is rejected. Supersedes role.")
}
// stringArg reads a string argument, tolerating a missing or non-string value.
// stringArg 读取一个字符串参数,容忍缺失或非字符串值。
func stringArg(m map[string]any, key string) string {
s, _ := m[key].(string)
return s
}
// boolArg reads a boolean argument, tolerating a missing or non-boolean value.
// boolArg 读取一个布尔参数,容忍缺失或非布尔值。
func boolArg(m map[string]any, key string) bool {
b, _ := m[key].(bool)
return b
}
// spawnLog returns l tagged with source "orchestration/spawn", replacing a nil
// logger with a discard one so the tool can always log.
// spawnLog 返回标注了来源 "orchestration/spawn" 的 l,并把 nil logger 换成丢弃式
// logger,使该工具总能记录日志。
func spawnLog(l *slog.Logger) *slog.Logger {
if l == nil {
l = slog.New(slog.NewTextHandler(io.Discard, nil))