[PRD] arise 运营与质量(决策快照 + 六池成本治理 + 管理命令)—— ADR-0010 完整落地 #77
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Problem Statement
从 host 部署者角度:arise 现在已经是一个会自己决定要不要说话、会主动找人、会开后台任务钻研数据、会自我学习调参的智能体,但部署者对它完全没有可见性也没有闸门——不知道它今天花了多少钱(没有任何 token 用量记账),不知道它为什么刚才没回那条消息(决策过程只有 15 处散落的非结构化
logger.debug文本,事后无从查起),没法在它开始烧钱时让某个能力降级或停下(六池预算/熔断全仓零实现),也没法在运行时调整任何东西(/setmodel//settool//reloadprompt//diagnose//cost//why//why_query七个管理命令一个都不存在)。装进一个真实、会持续运行数月的部署里,这几件事不是锦上添花,是能不能放心让它跑下去的前提。从维护者角度:CONTEXT.md/design.md 里"决策快照""成本治理""可解释性""可观测性""管理命令"整节都已经拍板(ADR-0010),Phase 4 PRD(issue #33)当时明确只交付了"决策快照的写入这一个最小必要切片",其余全部推迟。这是 design.md"上线路线"六个阶段里最后一个还没落地的阶段,也是 issue #33 排除清单里最后一项——做完这块,整条路线图第一次全部走完。
从调参角度:ADR-0011 的"阈值三层分类"明确写着标定方法是"保守默认 → 决策快照录制回放"。但决策快照至今不是结构化记录、不落盘、没有查询入口,这条标定路径实际上从来没有真正可用过——所有阈值默认值至今都还是"保守默认"这一半,另一半(按真实数据标定)缺的正是本次要建的基础设施。
Solution
一次性把 ADR-0010 运营面三块收口——它们互为前提,拆开做会让实现期反复回填基础设施(同 issue #33 当年把十条 ADR 一次性收口的理由):
addressed/skipped_message_ids。LLMClient契约让每次调用回传 token 用量(当前完全丢弃),按能力归入六池(reply/proactive/reflection/multimodal/embedding/delegate)记账,各池独立预算 + 超预算熔断(该能力降级或停),前台回复优先保。/cost查花费、/why拉最近一次决策解释、/why_query假设性查询调参、/setmodel、/settool、/reloadprompt、/diagnose。User Stories
部署者视角:成本可见与可控
部署者/用户视角:可解释性
/why就能看到我的私人记忆内容——"能管这个机器人"和"有权看某个人的记忆"是两码事,管理员身份不该成为绕过敏感度分桶/知情-gate 的后门。/why给我看的也是经过敏感度过滤后的内容——我要排查的是"它当时为什么这么决定",不需要、也不应该顺带拿到别人的私密信息。/why_query这类调参工具自己产生的消耗(它要算 relevance 就得真的 embed 一次查询)同样被记进池子——调参工具不能是记账体系的例外,否则"今天花了多少钱"这个数就是不准的。部署者视角:运行时管理
维护者/贡献者视角:可测性
验收标准视角
/why渲染记忆条目时按"命令是在哪个 chat 发起的"重新过一遍既有可见性闸——决策发生时对那个 chat 可见,不等于现在对发起/why的这个 chat 可见。/why_query的 embedding 消耗确实出现在 embedding 池的记账里;embedding 池已熔断时,/why_query明确失败而不是绕过熔断照常调用。Implementation Decisions
决策快照结构化
chat_id、触发路径类型、时间、门控各级得分/阈值/裁决(既有GateEvaluation已经完整产出这些,目前只是被格式化成日志文本丢掉)、情感态三轴、沉默预算余量、命中的未决意图、注入的记忆条目标识、addressed_message_ids/skipped_message_ids。logger.debug决策日志——日志本身不必全删(保留作运行时可观测性),但"事后能查"这个能力由结构化记录承担,不再依赖日志文本解析。/why只负责取最近一条记录 + 调用这个渲染函数。记忆条目只存标识 + 渲染时过敏感闸(隐私边界,不可省略)
/why渲染记忆条目时,按"命令是在哪个 chat 发起的"重新过一遍既有的可见性闸(events.is_event_visible(event, current_chat_id=<发起 /why 的 chat>),画像/Knowledge 等其它记忆类型同理走各自既有的同一套规则),不可见的条目降级为"有 N 条不可见条目参与了这次决策"之类的计数占位,不泄漏内容。source_ctx == current_chat_id,或私聊场景下当事人是知情参与者),它从来不是一个"谁有权看"的授权模型。决策发生时对 chat A 可见,不等于现在对发起/why的 chat B 可见;即便同一个 chat,"当时注入进模型上下文"与"现在打印给管理员看"也是两件不同的事,中间还隔着条目敏感度可能已被更新的时间差。is_admin授予的是"能不能管这个机器人",不该顺带成为绕过敏感度治理的后门。/why这条新出口上再犯一次。成本记账与六池治理
LLMClient契约携带 token 用量:AssistantTurn目前只有content/tool_calls,AnyLLMClient.complete()拿到 any-llm 响应后直接丢弃了 usage——这是六池治理绕不开的地基,必须先补上。新增用量字段(输入/输出 token),所有既有LLMClient实现(含测试用的ScriptedLLMClient)随之适配;embedding 客户端同理。RuntimeContext及各调度入口已经区分得很清楚)。host 工具产生的消耗不新开池,记进调用它的那条路径已有的池(ADR-0019 既定)。管理命令与权限
is_admin(chat_id, user_id) -> bool(同get_persona(chat_id)/get_tools(context)既有回调先例,core 不认识群主/管理员/superuser 这些平台概念,也不替 host 决定"谁算管理员")。挂进ArisePorts。允许了解我的动态/不再了解我的动态)是用户级授权(内容所有者本人同意),刻意不走管理命令的权限校验体系——ADR-0027 明确拒绝混用这两个授权维度。本次新增的权限校验只覆盖下述七个管理命令,不碰那两个。is_admin校验,校验失败明确拒绝(不静默忽略、不泄漏管理员才能看到的信息):/cost:查今日/本月各池已花费。/why:拉最近一次决策快照的可读解释。记忆条目按上述"渲染时过敏感闸"规则处理。/why_query:假设性查询——给定模拟的(query, 情感坐标),展示候选记忆的分维度得分明细(recency/importance/relevance/mood_congruence)。区别于/why只能回看真实发生过的一轮。候选数设静态上限。记忆条目同样过敏感闸(这条路径直接搜全库,比/why更需要,/why至少还受限于"当时真的注入过")。/why_query自己的 embedding 消耗必须记进embedding池:算relevance就要真的 embed 一次模拟查询,这是真实的 provider 调用,不是纯本地计算。调参工具不是记账体系的例外——否则"今天花了多少钱"这个数本身就不准(呼应 user story 6/13)。/why_query明确失败并说明原因,不绕过熔断照常调用。这是"熔断在发起调用之前"这条通用规则的直接推论,不是本命令的特例。/setmodel:运行时切换模型。/settool:per-chat 工具开关。/reloadprompt:重新加载 persona。/diagnose:依赖连通性自检(模型/数据库/向量库)。on_command(..., rule=to_me(), priority=1, block=True)形状(COMMAND_START含空串时裸文本会命中,to_me()收掉误触面——这个先例已经在跨平台拉取同意命令上验证过)。明确的范围边界
/why_query等一律是聊天命令形态(ADR-0010 更新节既定)。Testing Decisions
好的测试只测外部可观察行为(记账结果、熔断是否真的挡住调用、命令的可见效果、渲染输出),不测内部实现细节。
/why_query的分维度得分明细计算(复用既有recall.py打分纯函数,本次只测明细展开)。is_admin实现(恒真/恒假),断言恒假时命令无状态变更、无 LLM 调用(AC31),且拒绝信息不泄漏管理员才可见的内容。/why敏感闸(AC32/AC33):构造"决策发生在 chat A、/why从 chat B 发起、注入过的条目里有对 A 可见但对 B 不可见的敏感条目"这个场景,断言渲染输出不含该条目内容。这条最容易写成空转测试——如果只断言"输出里没有那段敏感文本",在渲染压根没实现条目展示的情况下同样成立;必须同时有反向对照(非敏感条目/同 chat 场景必须正常渲染出来),并建议做一次变异验证(把敏感闸那行删掉,确认测试真的会挂)。延续 issue #69/#70 两次被抓到同型问题的教训:断言"什么都没发生"时,通过原因往往不止一个。/why_query记账与熔断(AC34):断言跑一次/why_query后 embedding 池累计确实增加;embedding 池预设为已熔断时,断言假 embedding 客户端的调用计数为零(不是断言"没有输出结果"——那在多种原因下都成立,同上)。Out of Scope
/why_query等只做聊天命令形态,不新增 web 交互形态。/setmodel只做运行时手动切换,不做自动路由。MediaKind的"file"第四态:ADR-0028/CONTEXT.md 已决定但从未写进代码(见 issue #66 Further Notes 留痕),与本 PRD 无关,不顺手夹带。Further Notes
docs/design.md"上线路线"六个阶段将首次全部落地。届时值得做一次全文档核实(同深挖任务委托那轮的做法),确认路线图各阶段的承诺与实际实现逐条对得上——历史上已经出现过至少两处"文档说有、代码没有"的落差(深挖任务委托的"灰度可一键关"、MediaKind.file),全部走完之后正是系统性核对一遍的时机。/why的敏感闸这条约束是维护者在 PRD 评审时提出的,不是从 ADR-0010 原文推出来的——ADR-0010 写"决策快照记录注入了哪些记忆条目"时,隐含假设是"给系统自己看/给写代码的人看",没有考虑过它会经由一个管理员可见的聊天命令出口暴露出去。这是"把一个内部记录接上一条新的对外出口"时容易整类漏掉的问题:原记录的可见性假设未必还成立。未来若再给决策快照加新出口(比如导出、web 面板——虽然后者已被明确排除),需要重新做一次同样的检查,不能假设"快照里已经有的字段就是可以随便给出去的"。LLMClient/EmbeddingClient增加用量字段会影响所有既有实现和测试替身。这个改动本身很机械,但触及面广,实现期建议先单独把契约扩展做掉、确认全仓测试仍绿,再在其上做记账/预算逻辑——而不是混在一个 commit 里,否则一旦出问题很难区分是契约适配漏了还是记账逻辑错了。