docs(cyrene): cmd/cyrene 存量英文注释中文化(22 个非测试文件,仅注释)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled

- 四组并行完成:main/wire*.go、wire_capacity|channels|runtime、channels|persona_*|emotion|observability、
  voice|plugins|stream|skills|vision|subagent_tool|extractor|mcp|sessionread_tool|terminal_tool。
- 机械校验(git diff -U0 去 // 行与空行)仅剩 wire_frontend.go:84 一处行尾注释:代码前缀
  「\ta.gw = gw 」经 cat -A 与 HEAD 逐字节比对一致(allow 清单已声明)。
- 验收:隔离 worktree(HEAD + 本批 patch)go build/vet 绿、全树 58 包 ok、
  linux/amd64 + darwin/arm64 交叉编译绿;gofmt -l 干净。
- 备注:整树两次红均为负载敏感 flake(backthink / orchestration 的 timeout 类用例单独 -count=20 全过)。
This commit is contained in:
AskaEth committed 2026-10-04 16:58:06 +08:00
1 parent 5c2189efb0
commit 52f08dc343
22 files changed
+455 -586

No files matched your search

+23 -32
View File
@@ -8,22 +8,19 @@ import (
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/tool"
)
// This file builds the per-role agent pool (design/22 §4.2 方案 A). An
// agent.Agent's instructions are immutable after construction, so "the session's
// role decides the system prompt" is implemented as one agent per role, all
// sharing the same dispatcher, guardrail pipeline, model adapter and options —
// only the instruction string differs. The loop picks the pool entry for the
// turn's role on every step (internal/runtime/loop/persona.go), so a role switch
// takes effect on the next turn without rebuilding the loop.
// 本文件构建按角色的 agent 池(design/22 §4.2 方案 A)。agent.Agent 的指令在构造
// 之后不可变,因此「会话的角色决定 system 提示」被实现为每个角色一个 agent:它们
// 共享同一个 dispatcher、guardrail 流水线、模型适配器与选项 —— 只有指令字符串不同。
// 循环在每一步为当轮角色挑选池中条目(internal/runtime/loop/persona.go),因此角色
// 切换在下一轮即生效,无需重建循环。
//
// Requirements from design/22 §5: prompt, memory, emotion and thinking must all
// be resolved from the same role. The pool owns the prompt; the loop owns the
// other three (personaFor / knobsFor), so a turn can never mix two roles.
// design/22 §5 的要求:提示、记忆、情绪与 thinking 必须全部由同一个角色解析。
// 池拥有提示,循环拥有另外三者(personaFor / knobsFor),因此一轮绝不会混用两个
// 角色。
// personaAgentPool returns the per-role agent pool plus the agent to run turns
// with: the pooled agent for the default role when the pool has one, otherwise the
// caller's fallback agent, so a deployment with no registered character keeps
// exactly the pre-multi-character single agent.
// personaAgentPool 返回按角色的 agent 池,以及用来跑轮次的 agent:池中有默认角色时
// 用池里的那个,否则用调用方的兜底 agent,因此未注册任何角色的部署仍保持与多角色
// 之前完全一致的单 agent 形态。
func (a *app) personaAgentPool(llm *model.RuntimeLLM, dispatcher *tool.Dispatcher, guards *guardrail.Pipeline, fallback *agent.Agent) (map[string]*agent.Agent, *agent.Agent) {
pool := a.buildPersonaAgents(llm, dispatcher, guards)
if pooled, ok := pool[a.defaultPersonaID()]; ok {
@@ -32,22 +29,19 @@ func (a *app) personaAgentPool(llm *model.RuntimeLLM, dispatcher *tool.Dispatche
return pool, fallback
}
// buildPersonaAgents returns the role → agent pool: every registered character's
// persona gets an agent carrying that persona's system instruction
// (instructionsFromPersona, the very same assembly point as the default agent —
// see personaInstructions), design/01 §2.2. It returns an empty map when no
// persona is registered, so the loop keeps its single default agent.
// buildPersonaAgents 返回「角色 → agent」池:每个已注册角色对应的 persona 都得到
// 一个携带该 persona system 指令的 agent(instructionsFromPersona,与默认 agent
// 是同一个装配点 —— 见 personaInstructions),design/01 §2.2。未注册 persona 时
// 返回空 map,循环便保持其单一默认 agent。
//
// 池里的指令是**静态**的,因此主人身份说明书不在其中:那段知识按会话注入
// (loop.Options.PersonaKnowledgeFor),否则它会跟着每个角色进入群聊与
// 陌生人私聊(design/11 §0 红线)。
//
// Only personas reachable through a registered character are pooled: a character
// is the selectable unit (1 character ↔ 1 persona), so a persona with no character
// can never be bound to a session. A session bound to an unknown id therefore
// falls back to the default agent, and its memory scope still follows the bound
// id — a stale binding degrades to the default prompt rather than failing the
// turn.
// 只有通过已注册角色可达的 persona 才入池:角色才是可选单位(1 角色 ↔ 1 persona),
// 因此没有角色的 persona 永远无法绑定到会话。绑定到未知 id 的会话于是退回默认
// agent,而其记忆作用域仍跟随所绑定的 id —— 过期的绑定退化为默认提示,而不是让
// 该轮失败。
func (a *app) buildPersonaAgents(llm *model.RuntimeLLM, dispatcher *tool.Dispatcher, guards *guardrail.Pipeline) map[string]*agent.Agent {
log := logger.WithSource(a.log, "cmd/cyrene")
pool := make(map[string]*agent.Agent, a.personas.CountPersonas())
@@ -74,13 +68,10 @@ func (a *app) buildPersonaAgents(llm *model.RuntimeLLM, dispatcher *tool.Dispatc
return pool
}
// personaExists returns the resolver the orchestration layer validates a dispatched
// sub-agent's target role with (design/22 §8-2). A role is dispatchable exactly when
// the pool has an agent for it, which is precisely the condition under which the loop
// would answer with that role's instructions. Every other answer — an unregistered
// persona, a persona with no character, an empty pool — makes the dispatch fail with
// orchestration.ErrUnknownPersona instead of quietly running the sub-agent as the
// default role.
// personaExists 返回编排层用来校验被派发的子代理目标角色的解析器(design/22 §8-2)。
// 某角色可派发,当且仅当池中有它的 agent,而这正是循环会用该角色指令作答的条件。
// 其他任何情况 —— 未注册的 persona、没有角色的 persona、空池 —— 都会让派发以
// orchestration.ErrUnknownPersona 失败,而不是悄悄按默认角色运行子代理。
func personaExists(pool map[string]*agent.Agent) func(personaID string) bool {
return func(personaID string) bool {
_, ok := pool[personaID]