feat(cyrene,config,loop): 护栏/压缩/身份知识三条落地(P0-5/P0-10/P0-11)+ 身份注入收窄到主人会话
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled

- P0-5 护栏不再是空壳:挂 WithSensitiveInfo(阻断) + 长度上限(输入 262144 / 输出 65536 字符,依据=
  组装完成的请求体量级与「不误伤正常对话」) + 关键词**仅在配置时**挂;正常对话有用例证明放行
- P0-11 上下文压缩默认启用:CONTEXT_MAX_TOKENS 默认 32768(=loop.DefaultMaxContextTokens,两常量由测试锁死),
  0/负 = 关闭(行为退回接线前);长对话真压缩且摘要消息进请求首位、最近一轮保留
- P0-10 身份说明书(owner.yaml identityFile)注入人格上下文,并且**收窄为仅主人会话**:
  · 新增最小钩子 loop.Options.PersonaKnowledgeFor(env):**按该轮会话环境**解析(不看提示词、不看会话 id),
    块只进每轮视图、**不进 messages**(不进 checkpoint、不进记忆提取面)
  · 开关 OWNER_IDENTITY_INJECTION:owner_only(默认) / always(仅自测) / off;非法值与读不动 → WARN + 回退默认(不静默)
  · **顺手关掉第二条同型泄漏**:channelbridge 群聊参与度判定器的 PersonaPrompt 此前带身份正文 ⇒ 现改为纯人格指令
  · 回归:缺文件/目录/未配置逐字节等于旧输出;静态指令与角色池 Instructions() 全不含正文;
    文件里点名的陌生账号仍 IsOwner==false(身份知识绝不参与权限判定)
- 复核:build/vet 绿 + cmd/config/loop 包测试绿;四类断言(主人含 / **群聊与陌生人不含** / always·off 两档 /
  回归不变)+ 4 条变异反证(含把判定改成恒真 → 提示词里真的出现身份说明书)均已还原
- 未做(登记):knowledge 块启动时读一次(改文件需重启);`.env.example` 的 OWNER_IDENTITY_INJECTION 行随后补;
  `internal/user/owner.go` 注释与 design/01 §2.2、design/11 §0 待同步
This commit is contained in:
AskaEth committed 2026-10-04 14:30:26 +08:00
1 parent 19f296a2ab
commit da3a386daf
18 files changed
+1758 -81

No files matched your search

+55 -51
View File
@@ -102,23 +102,26 @@ type Loop struct {
personaName func(personaID string) string
personaKnobs func(personaID string) (LoopKnobs, bool)
personaAwakening func(personaID string) capacity.Awakening
timeLocation *time.Location
timeZoneName string
maxRounds int
toolExec string
toolParallelism int
hooks ToolLoopHooks
onStream func(model.StreamEvent)
thinking *model.Thinking
reasoningEffort string
thinkingShow *bool
onAudit func(AuditEvent)
onTurnEnd TurnEndHook
maxContextTokens int
compactor Compactor
transformContext func(context.Context, []model.Message, int) []model.Message
imageResolver ImageResolver
log *slog.Logger
// personaKnowledgeFor 是按会话解析「人格知识」前导块的解析器
// (Options.PersonaKnowledgeFor);nil 时每轮都不注入任何块。
personaKnowledgeFor func(SessionEnv) string
timeLocation *time.Location
timeZoneName string
maxRounds int
toolExec string
toolParallelism int
hooks ToolLoopHooks
onStream func(model.StreamEvent)
thinking *model.Thinking
reasoningEffort string
thinkingShow *bool
onAudit func(AuditEvent)
onTurnEnd TurnEndHook
maxContextTokens int
compactor Compactor
transformContext func(context.Context, []model.Message, int) []model.Message
imageResolver ImageResolver
log *slog.Logger
}
// New 由 opts 构建 Loop 并应用默认值。MaxRounds 非正时
@@ -145,40 +148,41 @@ func New(opts Options) *Loop {
}
timeLocation, timeZoneName := loadTimezone(opts.Timezone, sourcedLog(opts.Log))
return &Loop{
agent: opts.Agent,
agents: opts.Agents,
tools: opts.Tools,
capacity: opts.Capacity,
onCapacity: opts.OnCapacity,
memory: opts.Memory,
retriever: opts.Retriever,
working: opts.WorkingMemory,
extractor: opts.Extractor,
checkpoint: opts.Checkpoint,
tracer: opts.Tracer,
flow: opts.Flow,
guards: opts.Guards,
personaID: opts.PersonaID,
personaName: opts.PersonaName,
personaKnobs: opts.PersonaKnobs,
personaAwakening: opts.PersonaAwakening,
timeLocation: timeLocation,
timeZoneName: timeZoneName,
maxRounds: maxRounds,
toolExec: toolExec,
toolParallelism: toolParallelism,
hooks: opts.Hooks,
onStream: opts.OnStream,
thinking: opts.Thinking,
reasoningEffort: opts.ReasoningEffort,
thinkingShow: opts.ThinkingShowToUser,
onAudit: opts.OnAudit,
onTurnEnd: opts.OnTurnEnd,
maxContextTokens: opts.MaxContextTokens,
compactor: compactor,
transformContext: opts.TransformContext,
imageResolver: opts.Vision,
log: sourcedLog(opts.Log),
agent: opts.Agent,
agents: opts.Agents,
tools: opts.Tools,
capacity: opts.Capacity,
onCapacity: opts.OnCapacity,
memory: opts.Memory,
retriever: opts.Retriever,
working: opts.WorkingMemory,
extractor: opts.Extractor,
checkpoint: opts.Checkpoint,
tracer: opts.Tracer,
flow: opts.Flow,
guards: opts.Guards,
personaID: opts.PersonaID,
personaName: opts.PersonaName,
personaKnobs: opts.PersonaKnobs,
personaAwakening: opts.PersonaAwakening,
personaKnowledgeFor: opts.PersonaKnowledgeFor,
timeLocation: timeLocation,
timeZoneName: timeZoneName,
maxRounds: maxRounds,
toolExec: toolExec,
toolParallelism: toolParallelism,
hooks: opts.Hooks,
onStream: opts.OnStream,
thinking: opts.Thinking,
reasoningEffort: opts.ReasoningEffort,
thinkingShow: opts.ThinkingShowToUser,
onAudit: opts.OnAudit,
onTurnEnd: opts.OnTurnEnd,
maxContextTokens: opts.MaxContextTokens,
compactor: compactor,
transformContext: opts.TransformContext,
imageResolver: opts.Vision,
log: sourcedLog(opts.Log),
}
}