ADR 0196 — 重新启用的 Anamnese:没有主体就不运行,被接受的假设成为画像事实
状态
已接受。 2026-10-01(AT-277,故事 HS-072,史诗 EP-030,第 36 轮;所有者于 10月1日的决定:"religar a Anamnese com correções"——合格主体的守卫,以及被接受的假设成为图中的画像事实,并进入与此人对话的 agent 的上下文;被拒绝的只做记录)。只为 Anamnese 结束 2026-08-10 的暂停——Psicólogo 仍然暂停(PSYCHOLOGIST_ENABLED=false)。按 ADR 0099 设计的方式使用图(从事件日志派生的记忆),并使用语言的临时指引路径(RN-622),两者都不编辑。
背景
修改之前,在 dev 的代码中以及 2026-09-29 真实使用的报告中测得:
- 六轮,六个由模型写下的
anamnese.run_skipped,原因相同,“nenhum membro elegível”(会话be70、67f3、d7e9×2、5d66×2),6 次调用花费 8 933 微美元。anamnese_runs= 0,proficiency_profiles= 0。 - 主体从哪里来:
GetAnamneseContextUseCase读取ProjectRepository.listMembers,它只有project_members。创建项目不会在那里写入行(CreateProjectUseCase),而授权通过projectRole ?? workspaceRole(RN-471)把 workspace 的所有者视为项目成员。在一个人的项目中,列表总是空的。 - worker 不看这个列表:
AnamneseWorker.maybe_analyze/3只询问分诊(窗口的量、假设队列),而队列中的假设会强制一轮。prompt 写着“(nenhum membro elegível)”,模型被付费去得出这个结论。 - 被接受的假设实际上没有使用者:
AcceptHypothesisUseCase把它放入anamnese_queue(真实使用中 3 行),唯一的使用是某一轮的propose_instruction_patch——在5d66seq 17 中,它回答“não há arquivo de instrução de agente disponível no contexto”。图没有投影psychologist.hypothesis_accepted。 - 图的读取路径没有使用者:
QueryUserContextUseCase存在(按用户和项目的假设、画像、handoff),却没有任何代码调用它。 - 自 RN-540 起,
ANAMNESE_ENABLED在runtime.exs和三个 compose 中默认false;报告记录真实使用中的 engine 是通过环境把该标志设为true的。
决策
- 没有合格主体,轮次就不运行。
Engine.Anamnese.Elegibilidade在分诊之前决定,没有例外(假设队列也不能越过它):主体是项目的有效成员,不在退出名单中,在 prompt 显示的内容中有自己的交互——窗口中他的actor_kind: "user"事件,或他的决定。没有主体:不调用 LLM,不在 RAG 中搜索,在 engine 日志中写明原因(sem_sujeito_elegivel,带nenhum_membro或nenhuma_interacao_propria以及数字),并且只有在手动请求的轮次(origem: "manual")中,才有持久的anamnese.run_skipped。prompt 开始只列出主体。 - 项目成员是有效成员。
ProjectRepository获得listEffectiveMembers(membrosEfetivos,RN-471 的尺子:项目行在两个方向上覆盖 workspace 行),由 Anamnese 的三个用例使用。listMembers仍然只是project_members,也就是成员界面所编辑的。 - 被接受的假设成为画像事实**,从事件日志派生。**
psychologist.hypothesis_accepted开始携带projectId、sujeito(被分析会话的作者)、hipotese、sugestao和fatoDoPerfil。GraphEventTranslator——与投影器和grafo:reprojetar同一个——把它翻译为(:FatoDoPerfil {hypothesisId})-[:SOBRE]->(:Usuario)和-[:NO_PROJETO]->(:Projeto)(RecordProfileFactUseCase,按假设MERGE,新的唯一约束)。事件是自给自足的:重新投影不读取psychologist_hypotheses。 - 事实只来自主体本人的点击。 当接受者就是会话作者时,
fatoDoPerfil为true。第三方的接受对其他一切仍然有效(状态、Anamnese 的队列),并在 payload 中说明为什么没有成为事实(motivoSemFato)。被拒绝的不变:只做记录。 - 事实进入与此人对话的 agent 的上下文。
SendAgentMessageUseCase通过QueryUserContextUseCase读取消息作者的事实——限定在项目内,最近 5 条,旁边附总数——并以perfilDoAutor发给 engine,文本由textoDoPerfilDoAutor组装(最多 2 000 个字符,说明截断)。engine 通过与语言相同的路径把它们放进回合:控制器把两者合并成一个 map,IdiomaDaResposta.com_idioma_do_autor/2识别它,门面EngineApiClient在语言指引之前把文本作为临时的system消息追加(Engine.Harness.PerfilDoAutor)。没有任何 agent 服务器改变。图宕机时变成日志,回合在没有事实的情况下继续。 - 重新启用。
ANAMNESE_ENABLED的默认值在runtime.exs和三个 compose 中回到true(flags-do-engine-no-compose.spec.ts的尺子继续:compose 重复代码)。START_ANAMNESE在任何地方都不变:代码、开发和安装中为true;生产 compose 中为false,这一分歧已声明。PSYCHOLOGIST_ENABLED不变。
后果
- 9月29日的真实使用不再花钱:没有主体的项目到不了 LLM。而在没有自己行的项目中,workspace 的所有者开始成为主体——重新启用的 Anamnese 在最常见的情况下开始做点事情,而不是什么都不做。
- 声明的代价: Anamnese 现在会为只通过 workspace 成为成员的人建立画像。从未在项目中交互过的 workspace 成员不会被建立画像(不是主体),退出名单仍然有效。
- 声明的代价: 给对话式 agent 的每条消息都会在图中多一次读取(
QueryUserContextUseCase的四个查询)。没有配置 Neo4j 时,是立即的GraphUnavailableError加一条日志。 - 声明的代价: 事实会到达作者的每个回合,包括不是假设目标的 agent——标签说明它面向谁,按 agent 过滤是悬而未决的决定。
- 范围之外,已声明: 真实使用中的三次接受是在本次修改之前写入的,没有事实的字段,在重新投影中不会成为事实。它们仍在 Anamnese 的队列中,在下一次有主体的轮次中被读取。没有作者的回合(kickoff、dev agent、gate)不接收事实。Staff 仍然没有自动触发:Anamnese 在代码中从未触发过它。
- 没有主体的 tick 轮次不会变成事件(那会是每个项目每 15 分钟一个提示);只有手动请求的才会叙述。