Friend 通用记忆(Universal Memory)运维手册:控制开关、部署顺序与故障演练
【免费下载链接】FriendAI that sees your screen, listens to your conversations and tells you what to do项目地址: https://gitcode.com/GitHub_Trending/fr/Friend
本指南面向 backend on-call,完整讲解 Friend 后端"通用记忆"体系的运维控制面:统一规范写入器(universal canonical writer)、双格式读取器(dual-format reader)、历史兼容适配层(historical compatibility adapter)以及 Short-term 维护任务(memory-maintenance-job)的部署、观测、故障处置与回滚演练。读完本文,你将掌握全部运维开关的语义与取值、7 步部署顺序、无内容观测指标(Prometheus counters)、6 步故障动作以及强制回滚演练要求,并能直接定位到对应源码与运行时环境清单。
一、运营边界:一套权威,不做产品分群
Friend 的记忆体系遵循一条核心原则:每一个已认证用户都遵循同一套 memory/task 权威逻辑。仓库中不存在产品级 UID 注册(product UID enrollment)、dogfood 激活(dogfood activation)或通用账户回填(general account backfill)。开发环境中的 drain 操作 allowlist 只是安全围栏(safety fence),不是产品 enroll 机制。
- 所有新写入统一落入 canonical
memory_items; - 既有的
users/{uid}/memories历史行保持原样可读(read in place),仅在该条记忆被变更时才惰性物化(materialized)为 canonical 表示; - 读取侧通过 backend/utils/memory/memory_service.py 合并 canonical 条目(权威)+ 历史行(适配为发布响应模型,不原地修改)+ 持久化的 override/tombstone(抑制已物化或已删除的历史副本)。
这一边界在架构文档 backend/docs/canonical_memory_architecture.md 中有完整生命周期图与模块切面说明。
二、控制开关总览:全局就绪/事故/成本开关,绝不作为用户选择器
运维手册给出了一张权威控制表。以下逐一展开每个开关的语义、取值与源码佐证:
| 控制项 | Owner | 含义 |
|---|---|---|
MEMORY_MODE | backend + maintenance manifests | 全局就绪/事故声明;绝不是用户选择器 |
MEMORY_V3_GET_ENABLED | backend manifests | 已废弃、非权威的声明,等待 manifest 清理;绝不是用户选择器 |
MEMORY_CANONICAL_MAINTENANCE_ENABLED | 仅memory-maintenance-job | 启用计划性 Short-term 归一化、TTL 审计、consolidation 与 outbox drain |
MEMORY_CANONICAL_CONSOLIDATION_ENABLED | maintenance job | 全局 L2 成本/事故开关;必需处理、TTL 审计与 outbox 所有权保持独立 |
| consolidation batch/candidate caps | maintenance job | 限定单次 L2 调用与单轮 pass 的上界 |
KNOWLEDGE_LEDGER_DRAIN_ENABLED | knowledge-ledger-drain-job | 显式运维门;开发与生产 manifest 默认关闭。一次性开发执行仅在只读预检 + 直接用户 ledger 写入证明之后才可覆盖门 |
KNOWLEDGE_LEDGER_DRAIN_UID_ALLOWLIST | knowledge-ledger-drain-job | 开发环境保留一名具名 QA 负责人用于显式覆盖;生产环境为空。属于安全围栏而非产品 enroll,默认关门的配置防止计划性变更 |
| 共享 JIT admission + kill switch | knowledge-ledger-drain-job | 迁移前对每个账户和每行重新授权;授权不可用时 fail closed |
| ledger drain page cap/cursor | knowledge-ledger-drain-job | 全局滚动扫描每轮最多处理 20 个 apply-control 文档;仅当整页所有账户全部成功无错后才推进光标;dev allowlist 只读其具名 controls,不产生全局光标副作用 |
| cursor secret/version/TTL | backend | 活跃路由未使用;移除前需确认没有独立消费者占用该绑定 |
2.1MEMORY_MODE与MEMORY_V3_GET_ENABLED:遗留别名,勿再写入新 overlay
从 backend/config/memory_rollout.py 可以看到,真正的产品开关只有一个:MEMORY_ENABLED=on|off,未设置时 fail-closes 到off。MEMORY_MODE与MEMORY_V3_GET_ENABLED仅作为"单部署别名"保留,目的是让旧版本不爆炸:
- 未设置
MEMORY_ENABLED时,MEMORY_MODE=write|read→ 视为on,off|shadow→ 视为off; - 实时
GET /v3/memories路由不会调用MEMORY_V3_GET_ENABLED;列表 503 来自MEMORY_V3_CURSOR_SECRET/read_page,与该标志无关; MemoryRolloutMode枚举包含off / shadow / write / read四个值,其中MEMORY_ENABLED=on语义是 write-mode 摄入(create + list),不是Gate 3 只读。
在 backend/.env.template 中同样注明:"MEMORY_MODEandMEMORY_V3_GET_ENABLEDare legacy one-deploy aliases",并给出示例值MEMORY_MODE=off。
2.2MEMORY_CANONICAL_MAINTENANCE_ENABLED:仅memory-maintenance-job可置 true
实现位于 backend/utils/memory/canonical_short_term_maintenance_cron.py:
def canonical_maintenance_enabled() -> bool: """Whether *this process* hosts the ST→LT cron.""" raw = os.getenv(MEMORY_CANONICAL_MAINTENANCE_ENABLED_ENV, "false") return raw.lower() == "true"默认值是"false",这是刻意的 per-deployable 路由设计:只有memory-maintenance-job将其置为 true,且 backend/scripts/runtime_env_validation/manifest.py 会在任何其他 job 或请求路径 surface上设置该值时报构建失败。基础运行时环境清单 backend/deploy/runtime_env/_base.yaml 中该变量为value: 'false'、category: memory_rollout。
2.3MEMORY_CANONICAL_CONSOLIDATION_ENABLED:全局 L2 成本/事故开关
定义于 backend/utils/memory/canonical_consolidation.py:
MEMORY_CANONICAL_CONSOLIDATION_ENABLED_ENV = "MEMORY_CANONICAL_CONSOLIDATION_ENABLED" ... raw = os.getenv(MEMORY_CANONICAL_CONSOLIDATION_ENABLED_ENV, "true")它只控制 consolidation(Long-term 路由中的 L2 决策),必需处理(required processing)、TTL 审计、outbox 所有权保持独立——关闭它不会停掉其余维护管道。同一文件还定义了 consolidation 的上下文与重试上界常量,例如CONSOLIDATION_CONTEXT_CANDIDATES_PER_ANCHOR_MAX_COUNT = 20、CONSOLIDATION_CONTEXT_ARGUMENTS_MAX_CHARS = 2_000、CONSOLIDATION_CONTEXT_REDACTED_TEXT = "[REDACTED: restricted sensitivity]",这些即手册所述"consolidation batch/candidate caps 限定一次 L2 调用和一轮 pass"的具体来源。重试状态由ConsolidationRetryState承载(retryable / in_progress / quarantined / terminal_review四种状态 + lease 字段),支撑"修订级尝试、租约、有界重试、review quarantine"的防毒行机制。
2.4 ledger drain 两个开关:显式门 + 安全围栏
- backend/modal/knowledge_ledger_drain_job.py 的入口首先检查
ledger_drain_enabled_from_environment(),未开启直接return(打印knowledge-ledger-drain-job disabled);若开启但 allowlist 为空则抛错knowledge-ledger-drain-job requires an explicit UID allowlist。 - backend/utils/memory/knowledge_ledger_drain.py 定义了
MAX_LEDGER_DRAIN_UIDS_PER_RUN = 20、MAX_LEDGER_DRAIN_ERRORS = 16、LEDGER_ROW_AUTHORIZATION_TIMEOUT_SECONDS = 15.0,以及LEDGER_DRAIN_CURSOR_PATH = "knowledge_ledger_migration_control/inventory_cursor"。它刻意不依赖Short-term 维护模块,使用自己 generation-fenced 的持久光标,因此慢维护不会饿死 ledger 迁移。 - allowlist 解析
ledger_drain_uid_allowlist_from_environment()会拒绝含/或空白字符的 UID,且解析结果不出现在日志中。
生产与开发的差异可直接对照:
- 生产 overlay backend/deploy/runtime_env/prod.overlay.yaml:
KNOWLEDGE_LEDGER_DRAIN_UID_ALLOWLIST: value: ''(空,无 QA 围栏); - 开发 overlay backend/deploy/runtime_env/dev.overlay.yaml:保留一个具名 UID(
9OqYLlKJv4hmeYpIhwJcHBR975i2),即"开发保留一名具名 QA 负责人"的实际落地。
2.5 已退役的控制:MEMORY_ENABLED_USERS与代码内 UID 清单
MEMORY_ENABLED_USERS以及代码中自带的 product UID 列表均已退役。运行时校验(backend/scripts/runtime_env_validation/manifest.py 及 backend/scripts/runtime_env_validation/common.py)会拒绝任何"按用户细分的内存清单"重新引入。这与架构文档 backend/docs/canonical_memory_architecture.md 中"没有MEMORY_ENABLED_USERS运行时绑定、没有产品 enroll 命令"的声明一致。
三、部署顺序:7 步上线清单
运维手册给出了精确的 7 步部署顺序,每一步对应可验证的契约或观测点:
- 跑 hermetic 契约:对精确 SHA 运行 hermetic memory pipeline、universal service/mutation、task no-drop/no-duplicate、privacy/export、runtime-env 与 OpenAPI 兼容性契约(对应测试见 backend/tests/unit/test_universal_memory_service.py、backend/tests/unit/test_memory_apply_store.py)。
- 非生产项目部署该 SHA,准备 ≥2 个合成认证用户,覆盖三种数据形态:historical-only、canonical-only、mixed rows。
- 验证逻辑一致性:在 REST、chat、MCP、tools、developer 各 surface 上比对逻辑 ID、排序、lifecycle/device/visibility 策略、重复抑制与跨 UID 隔离完全一致。
- 验证维护任务:专用 maintenance job 与 Scheduler identity/cadence 正确;job 必须推进有界、无内容的账户注册表光标(bounded content-free account-registry cursor),不得扫描 users collection,也不得重复同一固定页。
- 验证
knowledge-ledger-drain-job与knowledge-ledger-drain-hourly:与 maintenance 相互独立,Scheduler 主账户对 job 持有run.invoker权限;确认其无内容计数器展示有界光标推进与 compatibility→ledger cutover。 - 演练全局停止与恢复路径:记录 revision、镜像 digest、project/database 身份、Scheduler/job 结果与无内容计数器。
- 先部署 universal reader 再开 canonical 摄入:在启用生产 canonical intake 或 maintenance之前先部署同一 universal reader;不要添加 canary UID 或 enroll 文档。
关于第 4、5 步中的 Scheduler 身份,可参考 backend/scripts/provision_daily_memory_sweep_scheduler.py:它保留了一份"来源派生"的契约映射EXPECTED_TARGETS = {EXPECTED_SCHEDULER_JOB: EXPECTED_CLOUD_RUN_JOB, "knowledge-ledger-drain-hourly": "knowledge-ledger-drain-job"},使用gcloud scheduler jobs create|update --http-method=POST --uri=https://run.googleapis.com/v2/projects/{project}/locations/{region}/jobs/{job}:run --oauth-service-account-email={service_account},每小时触发(0 * * * *,时区Etc/UTC),update 后还会显式resume以保证触发处于启用态。
四、无内容观测:只记计数,绝不记录内容
运维手册强调:观测只记录计数——canonical 行返回数、historical 行返回数、canonical-over-historical 抑制数、override/tombstone 抑制数、malformed historical 行数、cursor 失败数、pending/terminal 维护行数、outbox lag/dead letter 数、historical 清理失败数。不得记录memory 内容、embeddings 或 provider payload。
仓库通过 backend/utils/metrics.py 导出三个低基数的 Prometheus 计数器(文档原文,直接可用作告警与仪表盘表达式):
memory_universal_read_origin_total{origin="canonical|historical"}——universal repository 按物理来源纳入的逻辑行数;memory_historical_suppression_total{reason="canonical_identity|canonical_state"}——被 canonical 身份或 canonical 状态抑制的历史行数;memory_historical_materialization_total{outcome="not_needed|committed"}——惰性物化的结果分布(无需物化 / 已提交)。
这三个计数器在 backend/utils/metrics.py 中定义并完成 label 预注册。维护收据(maintenance receipts)额外上报:可重试的 outbox 失败数、dead letter 数、ack 失败数、已处理账户数、光标进度;告警聚合这些字段时禁止带 UID、memory ID 或内容标签。此外架构中还有一条文本无关的canonical_memory_decision_path.v1日志,只输出分类决策字段与计数,绝不输出 transcript、quote、memory 或模型 rationale 文本。
五、故障处置:6 步标准动作
当 incident 发生时,按以下顺序执行(不可跳步):
- 保持 universal dual-format reader 部署在位。回滚到 legacy-only reader 会隐藏新的 canonical 数据,明令禁止。
- 停 L2 成本或错误 consolidation:置
MEMORY_CANONICAL_CONSOLIDATION_ENABLED=false。 - 停计划性变更:置
MEMORY_CANONICAL_MAINTENANCE_ENABLED=false,同时继续权威读取。 - 若新摄入本身不安全:使用部署方拥有的全局 memory incident mode(对应
MEMORY_ENABLED关闭路径);绝不要把选定 UID 路由到 legacy writer。 - 保全数据:preserve canonical items、historical rows、overrides、tombstones、journals、outbox 状态;物理删除需单独审批。
- 修复后全局恢复:先运行一次有界维护执行(
gcloud run jobs execute memory-maintenance-job --wait),确认成功后再确认后续一次 Scheduler 执行与 provider lag 恢复。
其中第 3 步"继续权威读取"的支撑在于:维护任务设计为幂等(required normalization、TTL、全量 L2 路由、带租约的 projection-outbox 投递)。memory-maintenance-job的 Cloud Run Job 入口 backend/modal/memory_maintenance_job.py 会把outbox_delivery_failed与cursor_persist:标记为致命错误(_FATAL_ERROR_MARKERS),其余错误在 Flex 停止时视为非致命,避免重试整页;这正对应手册"restore globally, run one bounded maintenance execution with --wait, then confirm a later Scheduler execution"的恢复节奏。
5.1 维护任务的一次 pass 做了什么
从 backend/utils/memory/canonical_short_term_maintenance_cron.py 的run_universal_short_term_maintenance可看到单次执行的完整内部顺序:
- 通过"到期优先库存"(
expiry_ordered_maintenance_uid_inventory,只查未来 24h TTL 内的 lifecycle 元数据)与"有界注册表库存"(bounded_canonical_memory_uid_inventory,按持久化 UID 光标分页,MAX_MAINTENANCE_UIDS_PER_RUN = 400,到尾自动回绕)合并出本轮 UID 页; - 逐 UID 跳过无 active Short-term 的账户、
recently_dreamed(冷却DREAMING_MIN_INTERVAL = 20h,但 overflow > 10 条或到期紧急可绕过)与 ledger writer 模式账户; - 依次执行 required 归一化、TTL 过期结算、consolidation 路由(promote / archive / review / reject)、canonical apply 提交、outbox drain;
- 仅在整页无错误时用 generation 校验(CAS)推进注册表光标;
persist_cursor=False与"完成一个 UID 才提交"的设计确保 Flex 预算停止不会跳过后续账户。
对应测试覆盖见 backend/tests/unit/test_canonical_short_term_maintenance_cron.py:包括全局开关关闭时不解析库存(test_disabled_global_switch_does_not_resolve_inventory)、库存缺失 fail closed(test_missing_inventory_fails_closed_with_operational_dependency)、光标推进与回绕不饿死(test_registry_cursor_progresses_and_wraps_without_starvation)、Flex 停止不越过当前 UID(test_flex_deferral_stops_the_page_without_advancing_past_the_uid)、失败不启动冷却(test_failed_consolidation_does_not_start_dream_cooldown)等。
5.2 ledger drain 的独立光标与重授权
backend/utils/memory/knowledge_ledger_drain.py 展示 drain 的"整页提交"语义:run_knowledge_ledger_drain每轮经bounded_ledger_drain_inventory读取一页(≤20 个 apply-control 文档,按__name__排序 +start_after游标);对每个 UID 先做共享 JIT rollout 授权(resolve_jit_rollout(uid, stage=JITDecisionStage.INGRESS, force_refresh=True),15 秒超时,失败视为拒绝),授权通过才执行run_ledger_migration_sweep与publish_ledger_migration_cutover;任何账户级失败都不推进页面光标,且仅当not summary.errors时才commit_ledger_drain_inventory。这解释了手册中"页面推进仅发生在整页全部成功之后"以及"kill 掉的执行不会推进光标"的保证。
六、必需的回滚演练(Required rollback rehearsal)
部署任何版本前都必须完成回滚演练,演练需证明以下三点同时成立:
- 停止新的 canonical 处理不会隐藏历史或 canonical 数据(universal reader 保持读取两种格式);
- 不会恢复 legacy writer(不存在 UID 级回退通道);
- task intelligence 不会不可用(记忆权威缺失不连带影响任务智能)。
另外,在 provider(如向量库)排空期间,跨来源删除仍由 canonical tombstones/overrides 抑制——即删除/覆盖记录先于物理清理提交,确保重试、崩溃或 provider 中断期间不会出现重复读取或"复活"读取(backend/docs/canonical_memory_architecture.md 的 "Lazy historical mutation" 一节对此有完整描述:先校验历史行与请求 → 以相同 public ID 写 canonical 表示 → 提交 active override 或 tombstone → best-effort 清理历史行/向量;抑制记录提交先于清理)。
七、关键实现切面速查(Primary seams)
运维手册聚焦操作面,以下是其背后可直接查阅的实现切面(来自 backend/docs/canonical_memory_architecture.md):
| 关注点 | 代码 |
|---|---|
| 通用服务(universal service) | backend/utils/memory/memory_service.py |
| Canonical 适配器 | backend/utils/memory/canonical_memory_adapter.py |
| 历史 override 路径 | backend/database/memory_collections.py |
| 原子持久化 | backend/database/memory_apply_store.py |
| 必需处理 | backend/utils/memory/canonical_required_processing.py |
| 终态路由 | backend/utils/memory/canonical_consolidation.py |
| 计划编排 | backend/utils/memory/canonical_short_term_maintenance_cron.py |
| Outbox 投递 | backend/database/memory_outbox_worker.py |
| 公共 API | backend/routers/memories.py |
| 维护任务入口 | backend/modal/memory_maintenance_job.py |
| Ledger drain 入口 | backend/modal/knowledge_ledger_drain_job.py |
八、操作速查小结
- 想停 L2 成本:
MEMORY_CANONICAL_CONSOLIDATION_ENABLED=false(默认 true,仅维护任务生效)。 - 想停计划性变更:
MEMORY_CANONICAL_MAINTENANCE_ENABLED=false(默认 false,仅memory-maintenance-job可置 true,其他 surface 设置会构建失败)。 - 想全局停新摄入:关闭
MEMORY_ENABLED(产品唯一开关),或使用部署方 incident mode;禁止按 UID 回退到 legacy writer。 - 想跑 ledger cutover:开发环境在只读预检与直写证明后可临时置
KNOWLEDGE_LEDGER_DRAIN_ENABLED=true+ 具名 allowlist 执行一次性 drain;生产 allowlist 必须为空。 - 回滚底线:universal dual-format reader 永不回退;历史行物理删除必须单独审批。
- 观测纪律:只用无内容计数器与
canonical_memory_decision_path.v1分类字段,任何告警不得携带 UID、memory ID、embedding 或 provider 内容。
以上所有开关与步骤均可在 backend/docs/runbooks/universal-memory-operations.md 及其引用的源码、运行时环境清单(backend/deploy/runtime_env.yaml、backend/deploy/runtime_env/_base.yaml、dev/prod overlay)中交叉验证,适合直接用于 incident 排障与发布前 checklist。
【免费下载链接】FriendAI that sees your screen, listens to your conversations and tells you what to do项目地址: https://gitcode.com/GitHub_Trending/fr/Friend
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考