Repository navigation
Description
问题
人格卡现在只有一个寻址键:session.header.agentPreset(src/inject.js:315-324 → src/scope.js:79-80 → src/user-settings.js:54-62 的 resolvePersonaCard)。在 DS Bot 这类宿主里这个键是常量,所以 byAgent 永远只能命中同一张卡,做不到「一个 bot 一张卡」。
实测证据(2026-11-07,真实会话头)
扫 .dsh/sessions/ 下 50 个 bot 工作区会话的 session.v4.jsonl.zstd 首行:
| 字段 | 值 |
|---|---|
agentPreset |
standard 43 / minimal 3 / ptc 4 |
| 主 bot 会话(Amarre / Ephor / Loom / Persis 四条) | agentPreset 全部是 standard |
各 bot 的 id |
各不相同(session-d530e132…、session-389faaa7…、session-e0843aae…、session-ba2974ad…) |
即:agentPreset 分不开 bot,session.id 能分开。 面板按 bot 名字建卡永远命中不了(这也是 #587 那条「byAgent 整体为空时回落 default」的真实成因)。
设计
1. 存储:多一个维度,不动老形
user_settings.persona 从 { default, byAgent } 扩成 { default, byAgent, bySession }:
{
"default": { ...卡... },
"byAgent": { "<agentPreset>": { ...卡... } },
"bySession":{ "<sessionId>": { ...卡... } }
}
- 老数据零迁移:没有
bySession键读成{}(parsePersonaStore的兼容层已经在做同类判断,src/settings.js:57-79)。 serializePersonaStore(src/settings.js:81-93)同步带上第三维。- 新增
setPersonaForSession(sessionId, card)/removePersonaForSession(sessionId)/listPersonaSessions(),形状照抄既有的setPersonaFor/removePersona/listPersonas(src/settings.js:549-574)。
2. 寻址:会话 > 身份 > default
resolvePersonaCard 增加一个 sessionId 参数,优先级:
bySession[sessionId](且enabled === true)byAgent[agentId](且enabled === true)- 严格寻址判定(见下)
default
理由:会话是比预设更具体的寻址单位。DS Bot 里 agentPreset 恒为 standard,若 byAgent 优先,则谁给 standard 建了一张卡就会盖掉所有按会话建的卡。
严格寻址(personaStrictAddressing)沿用现有语义,但两个维度合起来看:任一维建过卡就拒绝兜底,一维都没建时仍回落 default(保住 #587 的口径)。
3. 身份解析:一次取全
src/inject.js 的 sessionAgentId(ctx) 改成 sessionIdentity(ctx),返回 { agentId, sessionId },两条取值各自护住(宿主 session 可能是会抛错的 accessor,sessionKeyOf 就是为同一类宿主写的)。renderUserSettingsBlock 相应收 sessionId。
4. 卡字段:补 scenario / mes_example
PERSONA_FIELDS(src/settings.js:37)现在是 ["name","personality","replyStyle","enabled","provenance"]。加 scenario 与 mesExample,渲染时排在 replyStyle 之后——未设置时零输出,所以老用户注入文本逐字节不变。
这两个字段是 #602 里「agent-persona 层要能容纳 scenario / mes_example」的最小落点:先有数据模型与渲染位,拆块本身仍归 #602。
验收
-
bySession存取往返:写入 → 读回 → 删除,getPersona()形状正确 - 老数据(只有
{default, byAgent},或 v1 单对象)读回后bySession为{},注入文本逐字节不变 - 寻址优先级:会话卡 > 身份卡 > default,各有独立用例
- 严格寻址:任一维建过卡时不兜底;两维都空时仍回落 default([人格] byAgent 卡按 agentPreset 寻址,而 DS Bot 所有 Bot 会话都是 standard:一人一张卡做不到,严格寻址下连唯一那张也被废掉 #587 回归)
-
scenario/mesExample未设置时输出与当前逐字节一致;设置后出现在replyStyle之后 - 注入链路端到端:构造带
header.agentPreset+id的 ctx,断言渲染用的是会话卡 - 面板预览路由
GET /api/dsh-mneme/persona?agent=…支持&session=… - 全量测试基线绿;
npm run sync后lib/与src/仍逐字节一致
不做(留后续)
- 面板 UI 的「按会话建卡」交互(本期只做存储 + 寻址 + 路由参数,面板另开)
- 轮次边界重注入(SPR):属于「人格漂移」那条线,见
D:\memory-hub\papers\persona-injection,与本 issue 无关 - 拆块(agent-persona / user-profile / scene):归 [Feature] 用户设置块把 Agent 人格与用户模型混在一起:按「谁的属性」拆成 agent-persona / user-profile / scene #602
Activity
modusensus commentedon Nov 7, 2026
判定:待定(设计成立,考期冻结令下顺延)
先把 issue 里的行号断言逐条核实了一遍,全部成立(基线
main @ 041e8f30):dsh-mneme/src/settings.js:37const PERSONA_FIELDS = ["name", "personality", "replyStyle", "enabled", "provenance"];dsh-mneme/src/settings.js:57-79parsePersonaStore:坏值回{ default: {}, byAgent: {} }、"default" in value || "byAgent" in value判多卡形、旧形整体作 default —— 兼容层在,但没有 bySession 分支。dsh-mneme/src/settings.js:81-93serializePersonaStore只写{ default, byAgent }。dsh-mneme/src/user-settings.js:54-62resolvePersonaCard的四步(byAgent → enabled 命中 → strict 判定 → default),resolveFactsCard:71-78同构且共用Object.keys(store.byAgent).length > 0。dsh-mneme/src/user-settings.js:88renderUserSettingsBlock({ settings, config, agentId = null, factsProvider = null })—— 签名里确实没有 sessionId。dsh-mneme/src/inject.js:315-324sessionAgentId(ctx)读ctx?.agent?.session?.header?.agentPreset,:336直接把它当 agentId 传进去。补三条 issue 没写、但这一版必须一起改的点
bySession整段抹掉(静默数据丢失)。dsh-mneme/lib/client.js:4426-4433的norm(c)只认name/personality/replyStyle/enabled四个字段 → 就算后端加了scenario/mesExample,过一遍面板就没了;dsh-mneme/lib/client.js:4642-4648的savePersonaCards是全量 PUT{ persona: { default, byAgent } }→ 只要面板存一次,库里bySession整段被覆盖掉。这条比"面板 UI 交互"更硬:它不需要新 UI 也会发生。dsh-mneme/src/api.js:598-605的 preview 现在只读url.searchParams.get("agent"),要加&session=才能预览按会话寻址的结果。dsh-mneme/src/lang.js:134-176里personaNameLine/personaPersonalityLine/personaReplyStyleLine齐备,但没有 scenario / mesExample 的键,中英各要新增一条。验收补一条
8 条验收里"bySession 优先于 byAgent"建议加一条反向锁:
byAgent有 enabled 卡、bySession也有 enabled 卡时,必须命中 bySession 且source报bySession(不是byAgent)——否则优先级写反了测试也不会红。顺带一个可复用口径
dsh-mneme/src/scope.js:144-153的sessionKeyOf(exec)已经给出 sessionId 的取值口径:const id = sessionOf(exec)?.id; return id == null ? null : String(id);,整体包在 try 里。sessionIdentity(ctx)照抄这一条即可,不要另起一套。排期
考前只修会耽误复习的故障 → 本项顺延考后,先把设计留在 issue 里。
Merge pull request #609 from slow-stack/feat/606-persona-by-session