端侧智能体如何只记习惯、不存原始内容
文章探讨 Android 智能体的跨会话记忆:不持久化屏幕、消息等敏感原始内容,而是记录任务意图、偏好应用等行为模式。通过 SessionPattern 数据结构说明如何表达这类长期偏好。
AI 深度解读
AI Agent 每次启动都从零认识用户,确实容易反复问同样的问题。但要让它跨会话记住习惯,是否就得保存聊天记录、屏幕内容和操作轨迹?StareBrain 的这篇 Android 实践给出了一种克制的做法:只记录任务类型、常用应用、步骤数和确认情况,再把这些模式交给模型参考。
这个方向适合想给 Agent 加「长期记忆」的程序员。不过,设计目标和代码实际做到的事情还有距离,尤其是如何判断用户不需要确认,不能只看字段名字。
跨会话记忆,可以只存任务的轮廓
系列 Part 14 处理的是 AgentContext 对当前场景判断错误的问题;Part 15 转向另一个问题:应用重新打开后,Agent 如何延续对用户习惯的了解。
作者划出的存储边界很明确:不持久化屏幕内容、消息正文和联系人姓名,只留下意图层面的行为模式。比如用户执行某类任务时,通常使用哪个应用、需要多少步、之前有没有经过确认。
下面的数据结构定义了这份记忆包含什么:
data class SessionPattern(
val intentType: String, // e.g. "send_message", "create_event", "open_file"
val preferredPackage: String?, // which app they use for this intent
val typicalStepCount: Int, // how many steps this usually takes
val confirmationHistory: ConfirmationHistory,
val lastSeenAt: Long
)
data class ConfirmationHistory(
val alwaysConfirms: Boolean, // user always confirms this action type
val neverConfirms: Boolean, // user consistently skips confirmation
val confirmCount: Int,
val skipCount: Int
)
要留意 preferredPackage:它保存应用包名,仍然能反映用户偏好。因此,这种设计减少了内容留存,却不能据此断言所有字段都不敏感。
这也不是一套完整的个人知识库。作者开头提到,用户说「发送文档」时,可能指 Google Drive 最近编辑的文件;但展示的结构没有文件标识,也没有最近编辑记录,无法单靠这份记忆完成文件定位。具体文件和收件人仍得从执行时的上下文获取。
任务完成后再记,规划时再读
作者把写入时机放在任务结束之后。SessionMemory 接收 SharedPreferences,recordCompletedTask 首先检查 TaskStatus.COMPLETED,未完成的任务直接返回。
这个边界有实际价值:进行到一半的尝试,不会立即被当成稳定习惯。不过,任务完成也只说明执行结束,不自动证明用户满意,原文没有提供纠错或反馈机制。
意图分类由设备端的 inferIntentType 完成,不调用 LLM。它按顺序匹配英文关键词:send 归入 send_message,schedule 或 calendar 归入 create_event,open 归入 open_file,pay 或 transfer 归入 payment,其余归入 general。
规则简单,也容易检查。但匹配有先后顺序,包含多个关键词的描述可能被提前归类;中文任务描述如何处理,原文没有说明。
记录时,preferredPackage 取任务第一步的 targetPackage,typicalStepCount 取 task.steps.size。这里的「偏好」和「典型」都比名字暗示的弱:前者可能只是起点应用,后者实际上是最近一次任务的步骤数,没有计算平均值或出现频率。
读取发生在 AgentContextBuilder.build 中。新增的可选参数 memory: SessionMemory? = null 配合 lastUserIntent 查找模式,再通过 knownPattern = intentHint 放进 AgentContext。模型由此获得规划提示,当前屏幕、应用切换和待执行任务依然来自实时上下文。
历史模式适合作为规划参考,不宜直接变成执行权限。
30 天过期很清楚,确认习惯却有歧义
作者用 lastSeenAt 控制记忆寿命,超过 30 天的模式不再注入上下文。下面的实现会在读取时顺便删除旧记录:
fun SessionMemory.loadPattern(intentType: String): SessionPattern? {
val pattern = loadRaw(intentType) ?: return null
val thirtyDays = 30L * 24 * 60 * 60 * 1000
return if (System.currentTimeMillis() - pattern.lastSeenAt > thirtyDays) {
deletePattern(intentType)
null
} else pattern
}
关键是 > thirtyDays 这一行:它按最后记录时间判断过期,而且属于读取时清理,不能理解成所有记录都会在第 30 天自动删除。
更需要检查的是确认历史。代码把 confirmedBy == ConfirmationSource.LLM_FLAGGED 的步骤计入确认次数,把 confirmedBy == null 且状态为 StepStatus.COMPLETED 的步骤计入跳过次数。
但「没有确认记录」不等于「用户主动跳过确认」。原文也没有解释 LLM_FLAGGED 为什么足以代表用户确认行为。
两个布尔值同样存在问题:累计确认次数 > 3 就设置 alwaysConfirms,累计跳过次数 > 5 就设置 neverConfirms。它们没有检查比例和相反记录,因此完全可能同时为真。模型面对的就不是清晰偏好,而是互相冲突的标签。
作者还举了「早晨用某个应用发送团队更新」的例子,但展示的代码没有统计时段。lastSeenAt 只是最后记录时间,不能直接支撑「经常在早晨」这个结论。
我的判断
我认可它优先限制存储内容的思路。相比保存完整对话或操作轨迹供后续检索,这份结构化摘要更容易检查,也更容易解释某条信息为什么进入模型上下文。
但原文展示的是实现方案,没有给出延迟、任务成功率、确认次数减少量或对照实验。「更快、更少打断」目前是目标,不能当成已经验证的收益。
它的主要局限也不在存储 API,而在行为建模太粗。同一种 send_message 可能对应不同应用和不同确认要求,按意图只保留一份模式容易互相覆盖。确认次数的累计规则,则连字段所表达的语义都没有完整实现。
对支付、转账这类动作,历史上没有确认记录,也不应成为今后免确认的理由。常用应用可以帮助模型排序候选方案,是否允许执行仍需要独立规则。原文没有展示这部分如何约束。
能拿来做什么
- 给日常 AI 编程助手增加记忆时,可以先保存任务类型、常用工具和完成状态等结构化摘要,并逐项确认字段是否确实需要跨会话保留。
- 如果任务来自自然语言,先检查意图分类是否覆盖实际输入语言;照搬这组英文关键词,无法可靠处理中文描述和复合任务。
- 实现确认历史时,应区分「用户明确确认」「无需确认」「用户拒绝」和「没有记录」,不要把缺失值直接算成用户偏好。
- 将历史模式接入上下文后,用应用偏好变化、模式超过 30 天、确认记录相互矛盾等具体案例验证行为,再决定它能影响哪些规划步骤。